fix(dashboard): startup schema reconcile no longer opens state.db as a second writable owner (#107688, salvage #107691 + #107701) - #107974
Conversation
994e9aa to
40f7b78
Compare
…uption Root cause: _eager_reconcile_own_session_db() called acquire() for an unconditional writable open. When gateway+dashboard share state.db, this creates two writable SessionDB owners — the documented FTS-rebuild corruption vector (PR NousResearch#93200, issues NousResearch#89293/NousResearch#90950). Solution: replace acquire() with _open_session_db_at_path(read_only=True), which still bootstraps a missing store and heals a stale schema via ONE writable open before reopening read-only, so the dashboard never holds a long-lived write lock that races FTS rebuilds. Fixes NousResearch#107688
…ses the handle Regression guard for NousResearch#107688 carried over from NousResearch#107701 (Rroven); extended to assert the startup handle is closed, not leaked. Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>
…opener `_open_session_db_for_profile(None, read_only=True)` is the path every dashboard router already takes for this process's state.db; the inline `Path(_default_db_path())` re-derived it. Docstrings now state the verified rationale (no second writable owner; the read path heals through one writable open when its probe fails) instead of asserting the FTS corruption mechanism the reviewer showed is already fenced by rebuild admission.
…s access-mode semantics to the opener The lifecycle docstring re-narrated _open_session_db_at_path's mechanics (two copies drift); the read-only test's docstring promised a heal it patches out.
40f7b78 to
1caab41
Compare
ehz0ah
left a comment
There was a problem hiding this comment.
Approval conclusion (formal approval unavailable without explicit repository access)
Reviewed exact head 1caab41045b92dfe2b441a08aec157996cd742e3 against base 4f137c73fcd689e5eb3187e47b223ad293f5f419.
Motivation
The dashboard startup reconciliation opened its own state.db through the writable registry path. On a healthy store that made the dashboard a second writable owner beside the gateway and allowed close-time checkpoint work that a read-only dashboard does not need.
Approach
This change routes startup through the existing profile-aware session opener with read_only=True, then releases or closes the returned handle through the shared lifecycle helper. It reuses the existing guarded bootstrap and schema-heal behavior instead of creating a second repair path.
Concrete changes
_eager_reconcile_own_session_db() now calls _open_session_db_for_profile(None, read_only=True). Healthy stores remain read-only. Missing, zero-byte, and stale stores can still perform the existing single writable bootstrap or reconciliation before reopening read-only. release_or_close() closes the standalone read-only handle without decrementing any registry-owned writer at the same path. The regression test verifies both the access mode and deterministic final close. The contributor mapping is limited to the test co-author carried into this combined PR.
Main-branch risk
I found no blocking regression. Generic corruption is still not escalated into a writable heal. Open and repair failures remain contained by the startup wrapper, and the existing polling path retains responsibility for later retries. Independent review also found no introduced double-close or leaked-handle path. The main residual risk is platform-specific SQLite behavior outside the focused test matrix.
Overall assessment
Approval conclusion. This is the smallest coherent combination of the prior proposals: it removes the unnecessary healthy-store writer, preserves guarded repair behavior, closes the returned handle, and adds the missing regression coverage.
Validation performed:
- Relevant session and schema suites: 222 passed, 1 skipped
- Regression applied to the base: failed as expected because the base still used the writable registry path
- Ruff on changed Python files: passed
git diff --check: passed- Hosted Docker amd64, Docker arm64, and Nix checks: passed
English verdict: APPROVE at exact head 1caab41045b92dfe2b441a08aec157996cd742e3. No blocking finding was identified.
|
Thanks for folding #107701 into this salvage — the combined approach is genuinely better than what I originally submitted. Two quick notes, from the author of the regression test:
One small thing worth confirming for the record: No further changes from my side — happy to help re-verify if needed. |
|
Landed on main via #110934 (d4063e6): your |
Pull request was closed
…9.11 ➔ v2026.9.14) (#790) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/gabrielcosi/hermes-agent](https://github.com/NousResearch/hermes-agent) | patch | `v2026.9.11` → `v2026.9.14` | --- ### Release Notes <details> <summary>NousResearch/hermes-agent (ghcr.io/gabrielcosi/hermes-agent)</summary> ### [`v2026.9.14`](https://github.com/NousResearch/hermes-agent/releases/tag/v2026.9.14): Hermes Agent v0.21.3 (v2026.9.14) [Compare Source](NousResearch/hermes-agent@v2026.9.11...v2026.9.14) ##### Hermes Agent v0.21.3 (v2026.9.14) **Release Date:** September 14, 2026 > Patch release. This tag rolls up the \~338 PRs merged since v0.21.2 into a stable tagged release for downstream consumers (Docker images, Hermes Cloud, hosted deployments). It exists so the remote-gateway sign-in fixes below reach Cloud agents, which auto-update to the newest release tag. ##### What this patch ships for remote Desktop / Cloud users - **Remote dashboard sessions no longer expire on refresh bursts** ([#​110061](NousResearch/hermes-agent#110061), fixes [#​55712](NousResearch/hermes-agent#55712); salvage [#​71548](NousResearch/hermes-agent#71548) [@​Doud-FR](https://github.com/Doud-FR), [#​55717](NousResearch/hermes-agent#55717) [@​liuhao1024](https://github.com/liuhao1024)). Both refresh paths on the gateway (cookie gate and the desktop's native bearer route) now coalesce concurrent requests carrying the same rotating refresh token, so a Desktop wake burst can no longer replay an already-rotated token into the Portal's reuse detection and revoke the whole session. Refresh also runs off the event loop, so a slow identity provider no longer freezes `/api/status`. Pairs with Portal-side [NousResearch/hermes-portal#1209](https://github.com/NousResearch/hermes-portal/issues/1209) (sliding 30-day idle horizon, 5-minute rotated-token grace). ##### Also requested for this tag - **Long-lived processes stop leaking duplicate state.db writer handles** ([#​110934](NousResearch/hermes-agent#110934), fixes [#​100896](NousResearch/hermes-agent#100896) [#​103339](NousResearch/hermes-agent#103339); salvage [#​107974](NousResearch/hermes-agent#107974) [@​kshitijk4poor](https://github.com/kshitijk4poor), mapping [@​Rroven](https://github.com/Rroven)): gateway, dashboard/Desktop backend, ACP and CLI readers attach read-only and in-process writers share the registry handle, so the `N live SessionDB handles` precursor stops firing on a healthy topology. ##### About this release Measured at commit `9b419a2d3c2657c192008e732149d61170b32c01`, the window since v0.21.2 contains **1,036 non-merge commits** across **2,642 changed files** (+131,690 / −37,096) and **338 merged PRs**. Also in the window, undocumented here on purpose: server→client JSON-RPC requests and a Pydantic wire-contract registry with generated TS/OpenRPC for the TUI/Desktop gateway ([#​110521](NousResearch/hermes-agent#110521), [#​110522](NousResearch/hermes-agent#110522)); reasoning-effort selection on every model picker, a composer pill and per-auxiliary control in Desktop; OpenRouter OAuth PKCE login; HEIF/HEIC/AVIF image decoding; the Honcho peer-model setup rework; MCP OAuth refresh tokens bound to their issuer; a daily MCP re-auth nudge in Desktop; Wan 3.0, Kling 3.0 / Kling Image v3, MiniMax H3 Max Turbo, Gemini Omni Flash 1.1 and Meta Muse in the FAL catalogs; Slack pasted tables and the Agent Sessions API; multiplexed-profile isolation and gateway-liveness fixes; and the state.db WAL refusal on cross-VM filesystems. **Full curated release notes for this window ship with v0.22.0**, which will document everything from v0.21.0 onward — highlights, feature areas, and complete contributor credits. Nothing in this window is skipped. ##### Updating - `hermes update` (git installs), or re-run the installer one-liner. - Docker / Hermes Cloud: images build from this tag (`nousresearch/hermes-agent:v2026.9.14`). **Full changelog:** <NousResearch/hermes-agent@v2026.9.11...v2026.9.14> </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Berlin) - 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 [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC44Mi4wIiwidXBkYXRlZEluVmVyIjoiNDQuODIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUvY29udGFpbmVyIiwidHlwZS9wYXRjaCJdfQ==--> Reviewed-on: https://git.xcd.dev/gabrielcosi/home-ops/pulls/790
The dashboard's startup schema reconcile of this process's own
state.dbnow opens read-only, so a store the gateway already owns never gets a second writableSessionDBowner.hermes_cli/web_server_lifecycle.py::_eager_reconcile_own_session_dbroutes through_open_session_db_for_profile(None, read_only=True)(the path every dashboard router uses) instead of the writable registryacquire(); the read path still bootstraps a missing store or heals a stale schema through ONE writable open when its read probe fails.release_or_close; one test asserts the read-only open and the close.Validation:
tests/hermes_cli/test_web_server.py188 passed; the new test goes red with main'sweb_server_lifecycle.py.Root cause: an unconditional writable
acquire()ran the full schema init plus a close-time WAL checkpoint beside the gateway's writer on every dashboard start.Credit: fix cherry-picked from #107691 by @kokhlo (first submitter); regression test from #107701 by @Rroven (Co-authored). Both authorships preserved in git history. Refs #107688 (reviewer note: the FTS-corruption mechanism claimed in the original PR is already fenced by rebuild admission; this lands as hardening, so
RefsnotCloses).Closes #107691, closes #107701.