fix: serve raw chat attachments from inbox - #2382
Merged
1 commit merged intoMay 16, 2026
Merged
1 commit merged into
1 commit merged into
Conversation
Michaelyklam
force-pushed
the
fix/issue-2380-attachment-file-raw
branch
from
May 16, 2026 12:08
75ee009 to
59b72fd
Compare
Michaelyklam
force-pushed
the
fix/issue-2380-attachment-file-raw
branch
from
May 16, 2026 12:14
59b72fd to
e4dad1c
Compare
This was referenced May 16, 2026
3de4338
eleboucher
pushed a commit
to eleboucher/homelab
that referenced
this pull request
May 16, 2026
… 0.51.75) (#527) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.74` → `0.51.75` | --- ### Release Notes <details> <summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary> ### [`v0.51.75`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05175--2026-05-16--Release-AY-stage-368--11-PR-safe-lane-batch--storage--i18n--run-journal-parity--attachments--compression-sidebar--restart-recovery--text-mode-images--tables--settings-i18n--German-labels) [Compare Source](nesquena/hermes-webui@v0.51.74...v0.51.75) ##### Test infrastructure - Stage-368 maintainer fix — pytest no longer self-loops on the `_schedule_restart` daemon thread. Several existing tests in `tests/test_update_banner_fixes.py` call `api.updates._schedule_restart()`, which spawns a daemon thread that eventually calls `os.execv()`. Those tests monkeypatch `os.execv` for the test scope, but monkeypatch teardown can win the race against the daemon thread, restoring the real `os.execv` before the thread fires it — at which point the daemon re-execs the entire pytest process with the original argv, looking from the outside like pytest hangs at 99 % then restarts the suite from 0 % in an infinite loop. `tests/conftest.py` now installs a permanent no-op wrapper on `os.execv` at module-import time so late-firing daemon threads cannot re-exec pytest. New `tests/test_pytest_execv_guard.py` pins the guard against future regressions. ##### Added - **PR [#​2377](nesquena/hermes-webui#2377 by [@​franksong2702](https://github.com/franksong2702) (refs [#​2283](nesquena/hermes-webui#2283), refs [#​2363](nesquena/hermes-webui#2363), refs [#​1925](nesquena/hermes-webui#1925)) — Run-journal replay timeline parity checks. After [#​2283](nesquena/hermes-webui#2283) shipped the first run-journal replay slice and [#​2363](nesquena/hermes-webui#2363) documented the cross-layer state consistency contract, this PR adds explicit parity assertions over the replayed timeline so divergences between the journal and the visible transcript (Thinking → tool calls → assistant text) surface as test failures instead of silent drift. ##### Fixed - **PR [#​2391](nesquena/hermes-webui#2391 by [@​Michaelyklam](https://github.com/Michaelyklam) (fixes [#​2389](nesquena/hermes-webui#2389)) — Reduce browser storage pressure during service-worker updates and over long-running sessions. `static/sw.js` now calls `deleteOldShellCaches()` BEFORE `caches.open(CACHE_NAME)` in the install handler so the new \~2.2 MB shell cache no longer overlaps the old one during a version bump (especially painful on shared-origin quota accounting). A new `_clearSessionViewedCount()` helper plus extended `_clearHandoffStorageForSession()` prune `hermes-session-viewed-counts`, `hermes-session-completion-unread`, and `hermes-session-observed-streaming` on every single-session delete and batch-delete so per-session tracking maps no longer grow unbounded. - **PR [#​2387](nesquena/hermes-webui#2387 by [@​Michaelyklam](https://github.com/Michaelyklam) (fixes [#​2386](nesquena/hermes-webui#2386)) — Guard `localStorage.setItem('hermes-webui-session', ...)` and workspace-panel runtime-state writes with `try { … } catch (_) {}` across `static/boot.js`, `static/sessions.js`, `static/commands.js`, and `static/messages.js`. These convenience writes were previously fatal UI operations on quota-exhausted browsers (especially Firefox public-domain setups where shared quota fills up after a service-worker shell rotation). - **PR [#​2368](nesquena/hermes-webui#2368 by [@​Michaelyklam](https://github.com/Michaelyklam) — Hybridize background profile env routing so background title generation, manual compression, and update-summary workers honor a session's non-default profile. The pure thread-local refactor for [#​2321](nesquena/hermes-webui#2321) was reverted because `hermes_cli.config.load_config()` still reads `HERMES_HOME` from process env. This PR keeps the thread-local layer for WebUI helpers and adds an `os.environ.update(runtime_env)` mirror under a narrow `_ENV_LOCK` for the worker body, with proper restore of prior values. New test asserts `OPENROUTER_API_KEY` is visible from the worker against a non-default profile. - **PR [#​2382](nesquena/hermes-webui#2382 by [@​Michaelyklam](https://github.com/Michaelyklam) (fixes [#​2380](nesquena/hermes-webui#2380)) — Serve raw chat attachments from the per-session inbox in addition to the session workspace. Chat uploads were intentionally moved out of workspaces into a per-session attachment inbox in an earlier release; the transcript renderer still emits stable `api/file/raw?session_id=...&path=<filename>` URLs, but `_handle_file_raw` only checked `session.workspace` so inbox-backed uploads rendered as broken images. The URL surface is preserved and a session-attachment fallback is added with path-traversal guards intact. - **PR [#​2385](nesquena/hermes-webui#2385 by [@​franksong2702](https://github.com/franksong2702) — Keep fuller compression snapshots reachable in the sidebar. The default behavior hides `pre_compression_snapshot: true` rows so archived compression segments do not duplicate the active continuation. A real long Kanban session exposed a narrower failure: the fuller transcript was still present on disk but remained marked as `pre_compression_snapshot`, so the sidebar surfaced a shorter row and the fuller transcript became unreachable. The fix preserves discoverability without re-introducing duplication in normal cases. - **PR [#​2371](nesquena/hermes-webui#2371 by [@​franksong2702](https://github.com/franksong2702) — Clarify interrupted turn recovery after a WebUI restart. WebUI executes browser-originated agent turns inside the WebUI process; if that process restarts mid-turn, the worker dies with it. Run journal replay can only replay events that were already emitted, so the stale-pending repair path is now annotated and refined to make the post-restart state explicit (interrupted, recoverable, or terminal) instead of leaving the user with a half-rendered turn and no signal. - **PR [#​2378](nesquena/hermes-webui#2378 by [@​Michaelyklam](https://github.com/Michaelyklam) — Strip historical images in text-only mode. Current-turn uploads already respect `agent.image_input_mode: text`, but saved conversation history still passed native `image_url` content parts back into later provider calls, breaking text-only providers on replayed turns. `_sanitize_messages_for_api()` gains a `cfg=` keyword argument so the API-history sanitizer can strip historical native image parts when the mode is text. Default `cfg=None` preserves prior behavior for callers that don't pass the new argument. - **PR [#​2375](nesquena/hermes-webui#2375 by [@​Michaelyklam](https://github.com/Michaelyklam) — Keep Markdown tables block-level. Pipe tables were already converted to `<table>` markup, but the final paragraph pass did not treat generated tables as block-level output, occasionally wrapping them in `<p>` and breaking the surrounding layout. The fix isolates generated tables and adds `table` to the paragraph-wrap skip list so valid CommonMark tables render predictably. - **PR [#​2372](nesquena/hermes-webui#2372 by [@​mccxj](https://github.com/mccxj) — Settings → Conversation page action buttons now respect locale selection. Pre-fix, the JSON export, MD export, and Copy buttons had hardcoded English labels/titles. Adds `data-i18n` / `data-i18n-title` attributes plus the missing translation keys so non-English locales no longer see English labels stuck in the middle of a translated screen. - **PR [#​2381](nesquena/hermes-webui#2381 by [@​Michaelyklam](https://github.com/Michaelyklam) (fixes [#​2379](nesquena/hermes-webui#2379)) — German relative session-time labels now interpolate the elapsed value instead of rendering the literal `{n}` placeholder in the sidebar/header. The German locale now uses function-valued translations for minutes, hours, and days, matching the other locale bundles. </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/527
SysAdminDoc
pushed a commit
to SysAdminDoc/hermes-webui
that referenced
this pull request
Jun 26, 2026
bernyforce
pushed a commit
to bernyforce/hermes-webui
that referenced
this pull request
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thinking Path
api/file/raw?session_id=...&path=<filename>URLs for uploaded images._handle_file_rawonly checkedsession.workspace, so inbox-backed uploads rendered as broken images.What Changed
_file_raw_target()to resolve/api/file/rawfirst from the workspace, then from_session_attachment_dir(session_id).Why It Matters
Uploaded chat images stored under
~/.hermes/webui/attachments/<session_id>/now load through the URL the frontend already generates, including docker-compose setups where the workspace and attachment root are separate.Verification
env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_sprint6.py::test_file_raw_serves_session_attachment_inbox tests/test_sprint6.py::test_file_raw_attachment_fallback_rejects_traversal tests/test_sprint6.py::test_file_raw_requires_session_id tests/test_sprint6.py::test_file_raw_unknown_session -q— 4 passedenv -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_sprint6.py tests/test_sprint1.py::test_upload_text_file tests/test_sprint1.py::test_upload_respects_attachment_dir_env tests/test_native_image_attachments.py -q— 56 passedenv -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_779_html_preview.py::test_inline_html_response_sets_csp_sandbox tests/test_sprint2.py::test_raw_endpoint_path_traversal_blocked tests/test_sprint6.py::test_file_raw_serves_session_attachment_inbox tests/test_sprint6.py::test_file_raw_attachment_fallback_rejects_traversal -q— 4 passedenv -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/ -q— 5734 passed, 6 skipped, 3 xpassed, 8 subtests passedpython -m py_compile api/routes.pygit diff --checkRisks / Follow-ups
_attachment_root()to the browser or switch the frontend to absolute media URLs._session_attachment_dir(session_id), not the attachment root, to avoid cross-session probing.Closes #2380
Model Used
AI-assisted change with repository inspection, targeted editing, and shell-based test verification.