From 0ba451d004a2c3227728f1b94a5e1670daf76449 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 29 Apr 2026 20:14:02 -0700 Subject: [PATCH] fix(vision): use HERMES_HOME-based cache dir instead of cwd (#17719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vision_analyze used Path('./temp_vision_images') — a relative path that resolved against cwd. Under Docker the image's WORKDIR is /opt/hermes, which is root-owned and only chmoded a+rX (read + traversal). Since #5811 landed (run as non-root hermes UID 10000, Apr 12), remote-URL vision calls fail with PermissionError on mkdir. Switch to get_hermes_dir('cache/vision', 'temp_vision_images'): resolves to $HERMES_HOME/cache/vision/ (= /opt/data/cache/vision/ in Docker — the user-owned volume mount). Existing installs with the old dir keep using it via the get_hermes_dir back-compat path; no migration needed. Only site in the codebase that stored runtime files via Path('./...'). Reported via Discord: https://juick.com/i/p/3089079.jpg → Telegram → gateway → [Errno 13] Permission denied: 'temp_vision_images'. --- tools/vision_tools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/vision_tools.py b/tools/vision_tools.py index 6ad885cb..233b7372 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -38,6 +38,7 @@ from typing import Any, Awaitable, Dict, Optional from urllib.parse import urlparse import httpx from agent.auxiliary_client import async_call_llm, extract_content_or_reasoning +from hermes_constants import get_hermes_dir from tools.debug_helpers import DebugSession from tools.website_policy import check_website_access @@ -435,7 +436,7 @@ async def vision_analyze_tool( Exception: If download fails, analysis fails, or API key is not set Note: - - For URLs, temporary images are stored in ./temp_vision_images/ and cleaned up + - For URLs, temporary images are stored under $HERMES_HOME/cache/vision/ and cleaned up - For local file paths, the file is used directly and NOT deleted - Supports common image formats (JPEG, PNG, GIF, WebP, etc.) """ @@ -483,7 +484,7 @@ async def vision_analyze_tool( if blocked: raise PermissionError(blocked["message"]) logger.info("Downloading image from URL...") - temp_dir = Path("./temp_vision_images") + temp_dir = get_hermes_dir("cache/vision", "temp_vision_images") temp_image_path = temp_dir / f"temp_image_{uuid.uuid4()}.jpg" await _download_image(image_url, temp_image_path) should_cleanup = True