Stage 272: 3 PRs — #1493 sidebar cancel + #1495 state.db FD leak fix + #1492 P0 polish bundle (closes #1466 #1469 #1484 #1486 #1494; refs #1458 Bug #2)#1496
Merged
nesquena-hermes merged 8 commits intomasterfrom May 3, 2026
Conversation
…ning - #1481: Use absolute path for service worker registration to avoid <base> tag resolution on session pages causing JSON 404 - #1484: Fix tool-card expanded args readability — replace word-break:break-all with pre-wrap+break-word, add display:block so newlines and indentation are preserved - #1486: Prefer WebUI JSON title over state.db title for CLI sessions, fixing rename-not-persisting after compression chain extension - #1469/#1360: Add _programmaticScroll guard to distinguish programmatic scrolls from user scrolls, preventing the race condition where scrollIfPinned() re-pins after user scrolls up
… sidebar polling (#1494) Production WebUI on macOS launchd reproduced an HTTP-unhealthy wedge after #1483 closed the bootstrap supervisor double-fork: process alive, port listening, every HTTP request reset by peer before a response. The reporter (@insecurejezza) traced it to FD exhaustion — 366 open FDs on the wedged process, 238 of them `~/.hermes/state.db`, `state.db-wal`, and `state.db-shm`. Root cause: four sqlite callsites use `with sqlite3.connect(...) as conn:`. Python's sqlite3 connection context manager only commits or rolls back on exit; it does NOT close the connection. `/api/sessions` polling calls these on every sidebar refresh, so each poll leaked one or more open state.db FDs until the process hit macOS's soft FD limit and new sqlite3.connect() calls inside fresh request handlers raised before any response bytes were written. Fix: wrap each `sqlite3.connect(...)` in `contextlib.closing(...)` so the connection is explicitly closed on scope exit, in addition to the auto- commit / rollback semantics that `Connection.__exit__` already provides. Callsites patched: - api/agent_sessions.py:read_importable_agent_session_rows - api/agent_sessions.py:read_session_lineage_metadata - api/models.py:get_cli_session_messages - api/models.py:delete_cli_session Reporter's verification (post-patch, 100-request stress loop against /api/sessions and /api/projects): batch=1 fd=92 state_handles=0 batch=2 fd=92 state_handles=0 ... batch=5 fd=92 state_handles=0 Pre-patch the same loop made FD count and state.db handle count climb monotonically. 4 regression tests in tests/test_issue1494_state_db_fd_leak.py monkeypatch sqlite3.connect with a tracking wrapper that records .close() calls and assert every connection opened by each of the four functions is explicitly closed. Verified to fail (catching the original bug) when the closing() wrap is reverted: "leaked 5 of 5 sqlite connection(s) — context-manager- only `with sqlite3.connect()` does not close. Wrap in contextlib.closing()." This addresses Bug #2 of the umbrella issue #1458. Bug #3 (HTTP-unhealthy wedge in the absence of FD exhaustion) remains open pending separate diagnostic data — explicit scope discipline. Closes #1494 Refs #1458 (Bug #2 of 3) Co-authored-by: insecurejezza <[email protected]>
- Revert '/sw.js' back to relative 'sw.js' in serviceWorker.register() (static/index.html:50). The dynamic <base href> script resolves relative paths correctly for both root and subpath mounts. Absolute path breaks reverse-proxy installs at e.g. /hermes/. - Add regression test test_index_sw_registration_uses_relative_path to prevent future absolute-path rewrites from silently breaking subpath-mount installs. Addresses reviewer feedback on PR #1492 (review by @nesquena).
…ll pinning + sw.js relative-path regression test)
…ix + P0 polish bundle (#1466 #1494 #1469 #1484 #1486) 3 PRs in this batch (3866 → 3874 tests, +8): - #1493 (@dso2ng) — sidebar Stop response cancels row's stream not active pane's (closes #1466, follow-up to #1480) - #1495 (self-built; reported by @insecurejezza in #1494) — state.db connection FD leak in sidebar polling (closes #1494, addresses Bug #2 of #1458) - #1492 (@bergeouss) — P0 bugfixes bundle: tool-card args readability + CLI rename persistence + scroll pinning + sw.js relative-path regression test (closes #1469 #1484 #1486) This release closes Bug #2 of the umbrella issue #1458. Bug #1 was closed by v0.50.269 (#1483) + v0.50.270 (#1487). Bug #3 (HTTP-unhealthy without FD exhaustion) is the remaining work item.
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.
Stage 272 release — 3 PRs
This release PR shepherds three contributor + self-built PRs to master with
--no-ffmerges.Constituent PRs
static/index.htmlmatches master byte-for-byte at line 50 and the regression test correctly enforces the relative-path invariant.Test posture
3866 → 3874 tests passing (+8: #1493's 3 + #1495's 4 + #1492's 1).
Pre-release Opus advisor passes
Initial 2-PR pass (#1493 + #1495): ship-as-is. Two non-blocking SHOULD-FIX deferred to follow-up.
Extended 3-PR pass (added #1492): ship-ready. All four risk areas check out:
Session.load_metadata_only(sid)perf cost is bounded (~5ms for 200 sessions, dominated bystat()early-returns).setTimeout(0)race is real but benign — terminal state is always correct."'/sw.js?v=" not in srcis precise (the leading'/makes it specific to the absolute form) and won't false-positive.One Opus concern about CLI session IDs hitting
load_metadata_only's[0-9a-z_]validator was investigated maintainer-side — real CLI session IDs useYYYYMMDD_HHMMSS_<6hex>format which fully matches the validator. Mooted by reality.Release framing
Closes #1466
Closes #1469
Closes #1484
Closes #1486
Closes #1494
Refs #1458 (Bug #2 of 3)