fix(state): opening state.db no longer takes the write lock when nothing needs writing (salvage #101881) - #106751
Closed
kshitijk4poor wants to merge 4 commits into
Closed
kshitijk4poor wants to merge 4 commits into
kshitijk4poor wants to merge 4 commits into
Conversation
kshitijk4poor
enabled auto-merge (rebase)
September 9, 2026 17:31
kshitijk4poor
disabled auto-merge
September 9, 2026 17:59
…hat needs no writes Every read-write SessionDB open issued three writes that usually change nothing, and a write statement takes the database write lock even when it matches no rows: - _ensure_db_file_generation's INSERT OR IGNORE into state_meta. The stamp is minted once per FILE, so every open after the first inserted nothing. - the NULL-`active` heal, UPDATE messages SET active = 1 WHERE active IS NULL, which matches nothing on a healthy database. - the fts_storage_version stamp, which re-wrote the same value on every open of an already-optimized database. The connection is opened with timeout=1.0, so each blocked write costs a full busy timeout while a sibling process holds the write lock, and the open path's patience loop can ultimately give up and raise. Gate all three on a read. Measured on a real 99 MB state.db (239 sessions, 6470 messages) with a sibling holding the write lock: 2117-2136 ms -> 3.6-7.1 ms. On an already-optimized database the unpatched open does not merely stall, it raises `database is locked`; patched it completes in 3.7-6.8 ms. With a sibling running 200 ms write transactions in a loop (n=20 opens): p50 894.6 -> 9.6 ms, p90 1094.7 -> 13.9 ms. A settled database now issues zero main-database write statements to open. The reads cost nothing measurable: the state_meta probe is a primary-key seek (2.0 us), the messages probe is 1.7 us on the modern NOT NULL column (unsatisfiable constraint, short-circuited) and 0.4-0.6 us at 300k rows on a legacy default-less column via the partial index that already exists for exactly this predicate. An uncontended open is unchanged. Semantics are preserved. INSERT OR IGNORE still resolves the first-opener race inside SQLite and racers still converge on the winner's token via the re-read; the application_id gate and the PASSIVE-only checkpoint are untouched; the heal is still considered on every startup, as NousResearch#60108 deliberately made it, with only the write now conditional on a read proving there is something to repair. Read-first also fixes a correctness bug. Under contention the generation block was abandoned by its `except sqlite3.Error` handler, so a process ended up with no generation token at all even though the value was already on disk and a plain read would have returned it -- and that token feeds the deleted-WAL and replaced-file guards added by NousResearch#101221. The heal's `except OperationalError: pass` likewise skipped the repair silently, so the unconditional form did not even deliver the unconditional repair it advertised whenever it mattered most. The probe deliberately does not use INDEXED BY: that hint raises OperationalError("no query solution") against the modern NOT NULL column, and the existing handler would swallow it, disabling the repair forever.
…rites on settled open; open not blocked by sibling write lock)
…already match DEFERRED_INDEX_SQL unconditionally DROPs and re-CREATEs the four display triggers on every open so a changed trigger body rolls out; DROP TRIGGER IF EXISTS on an existing trigger takes the write lock, so a settled database still blocked behind a sibling's transaction after the three statement gates. Compare each trigger's stored sql against the desired CREATE text and run the pair only when they differ. CREATE ... IF NOT EXISTS forms are lock-free on an existing object and run as written. The no-writes test now also traces DROP TRIGGER / ALTER.
kshitijk4poor
force-pushed
the
salvage-101881
branch
from
September 9, 2026 18:13
ed21f8d to
b746ca7
Compare
kshitijk4poor
enabled auto-merge (rebase)
September 9, 2026 18:15
Contributor
PR #106751 — fix(state): opening state.db no longer takes the write lock when nothing needs writingSummary: SessionDB open path was issuing write statements (INSERT OR IGNORE stamp, DROP+recreate triggers, unconditional UPDATE, meta re-stamp) that take the write lock even when changing nothing, blocking opens behind a sibling's transaction. Now: read-before-write everywhere + DDL that skips settled triggers. Pinned by a write-tracing test and a sibling-hold test. Findings (all Non-blocking):
Solid lock-hygiene fix with honest tests. No blocking issues. |
Collaborator
|
Landed on main in #108067 (ff59fcd). All three commits cherry-picked with authorship intact (and #101881 by @jonpol01 underneath); thank you @kshitijk4poor. |
auto-merge was automatically disabled
September 11, 2026 13:44
Pull request was closed
Closed
13 tasks
gabrielcosi
pushed a commit
to gabrielcosi/home-ops
that referenced
this pull request
Sep 12, 2026
…9.7 ➔ v2026.9.11) (#754) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/gabrielcosi/hermes-agent](https://github.com/NousResearch/hermes-agent) | patch | `v2026.9.7` → `v2026.9.11` | --- ### Release Notes <details> <summary>NousResearch/hermes-agent (ghcr.io/gabrielcosi/hermes-agent)</summary> ### [`v2026.9.11`](https://github.com/NousResearch/hermes-agent/releases/tag/v2026.9.11): Hermes Agent v0.21.2 (v2026.9.11) [Compare Source](NousResearch/hermes-agent@v2026.9.7...v2026.9.11) ##### Hermes Agent v0.21.2 (v2026.9.11) — The state.db Patch Release **Release Date:** September 11, 2026 > Patch release. v0.21.0 shipped a large rewrite of the session store's connection handling, and for some installs it made `state.db` fragile: second writers cancelling each other's locks, healthy databases reported as corrupt, one bad row killing `sessions list`. This release closes that class and rolls up everything else that landed on `main` in the four days since v0.21.1. ##### About this release Measured at commit `04dd80a977f40b05e5b2054111747af07a61886a`, the window since v0.21.1 contains **947 non-merge commits** across **1,869 changed files** (+182,504 / −15,564) and **312 merged PRs**. **140 contributors** appear in commits, co-author trailers, or salvage credits. ##### ✨ Highlights ##### state.db reliability campaign (six PRs, 44 issues closed) If your `state.db` broke after 0.21.0, this is the release for you. Six PRs fix the root causes rather than the symptoms: - **No more second writers.** Profile gateways wrote hosted-room state into the *root* `state.db` every 5 seconds; the dashboard opened a writable handle on startup; cron's lifecycle guard did a raw `open()` on a live database (which cancels the gateway's POSIX locks — the classic "how to corrupt SQLite" recipe); `doctor --fix` would checkpoint under a live holder. All four are gone: hosted rooms live in `shared-state.db`, the dashboard opens read-only first, the guard goes through the tracked connection registry, and `doctor --fix` refuses a checkpoint it can't prove is safe. ([#​108076](NousResearch/hermes-agent#108076) — salvage [#​103489](NousResearch/hermes-agent#103489) [@​RikETS](https://github.com/RikETS), [#​102682](NousResearch/hermes-agent#102682) [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [#​108012](NousResearch/hermes-agent#108012) [@​Halldrix](https://github.com/Halldrix), [#​105428](NousResearch/hermes-agent#105428) [@​TaoMasterCoder](https://github.com/TaoMasterCoder)) - **Healthy WAL databases stop wedging.** OpenZFS `(deleted)` dentries and a `close()` racing an `append_message` both produced a sticky `DeletedWalGenerationError` on a perfectly good store; the read pool was handed out under an unconfirmed journal mode; a transient `disk I/O error` on WSL2 killed `get_session` on the first attempt; and a "state.db locked" banner was broadcast after the lock had already cleared. ([#​108082](NousResearch/hermes-agent#108082) — salvage [#​107411](NousResearch/hermes-agent#107411) [@​chelsealong](https://github.com/chelsealong), [#​105578](NousResearch/hermes-agent#105578) [@​ca-shrimp](https://github.com/ca-shrimp), [#​105711](NousResearch/hermes-agent#105711) [@​gaoanze888](https://github.com/gaoanze888), [#​106958](NousResearch/hermes-agent#106958) [@​nikkoxgonzales](https://github.com/nikkoxgonzales); co-authored [@​QDung210](https://github.com/QDung210), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya)) - **FTS damage no longer kills your turn.** An error scoped to the full-text-search index was classified as whole-file corruption and fail-closed the conversation. It's now `fts_index`: search degrades, the index rebuilds later, the transcript store is untouched. Same PR: doctor names structural damage honestly instead of "FTS write corruption", the FTS write probe catches the stale-index shape that passed every check while every write failed, `.recover` output no longer fails startup on orphan FTS5 shadow tables, header-zeroed databases recover instead of being refused, and the dashboard analytics poller returns a 503 instead of 520K tracebacks a day. ([#​108130](NousResearch/hermes-agent#108130) — salvage [#​97843](NousResearch/hermes-agent#97843) [@​SulthanZahran1](https://github.com/SulthanZahran1) + [#​97841](NousResearch/hermes-agent#97841) [@​Finn763](https://github.com/Finn763), [#​88604](NousResearch/hermes-agent#88604) [#​56824](NousResearch/hermes-agent#56824) [#​103657](NousResearch/hermes-agent#103657) [@​liuhao1024](https://github.com/liuhao1024), [#​106890](NousResearch/hermes-agent#106890) [@​nftpoetrist](https://github.com/nftpoetrist), [#​103321](NousResearch/hermes-agent#103321) [@​jangomango76](https://github.com/jangomango76), [#​91413](NousResearch/hermes-agent#91413) [@​leegunwoo98](https://github.com/leegunwoo98), [#​102808](NousResearch/hermes-agent#102808) [@​TaoMasterCoder](https://github.com/TaoMasterCoder)) - **One corrupt row no longer kills `sessions list`, export, or insights.** A TEXT timestamp or a `1e30` epoch used to crash the whole listing; malformed marker JSON crashed `json_extract`; more than 999 ids crashed bulk delete/prune. One `coerce_epoch()` helper on every reader (bad rows render `?` with a WARNING naming the session), a `json_valid` guard, IN-list chunking, and batched export hydration. ([#​108086](NousResearch/hermes-agent#108086) — salvage [#​106071](NousResearch/hermes-agent#106071) [@​Xipong](https://github.com/Xipong), [#​101726](NousResearch/hermes-agent#101726) [@​efe-arv](https://github.com/efe-arv), [#​94701](NousResearch/hermes-agent#94701) [@​liuhao1024](https://github.com/liuhao1024), [#​102679](NousResearch/hermes-agent#102679) [@​mssteuer](https://github.com/mssteuer), [#​100658](NousResearch/hermes-agent#100658) [@​Mi55ed](https://github.com/Mi55ed)) - **Sessions never bind to or read another profile's database.** The Desktop launch backend could pin itself to the wrong profile's `state.db` under a HERMES\_HOME override race; `session_search` by bare ID silently scanned every profile and returned someone else's transcript; recovery guidance pointed at the wrong file; profile delete kept a handle open (WinError 32). ([#​108074](NousResearch/hermes-agent#108074) — salvage [#​102534](NousResearch/hermes-agent#102534) [@​HexLab98](https://github.com/HexLab98), [#​106975](NousResearch/hermes-agent#106975) [@​Sora-bluesky](https://github.com/Sora-bluesky)) - **Opening state.db no longer takes the write lock when nothing needs writing.** A one-shot `hermes` process opening the store behind a busy gateway stalled 4–20 s and then failed with "database is locked". Now 0.01 s. ([#​108067](NousResearch/hermes-agent#108067) — salvage [#​106751](NousResearch/hermes-agent#106751) [@​kshitijk4poor](https://github.com/kshitijk4poor), [#​101881](NousResearch/hermes-agent#101881) [@​jonpol01](https://github.com/jonpol01)) Also in the window from the same subsystem: a fresh `state.db` no longer publishes FTS tables before owning the rebuild lock ([#​106311](NousResearch/hermes-agent#106311)), a handle that lost its WAL generation no longer checkpoints stale frames at shutdown ([#​106315](NousResearch/hermes-agent#106315), [#​106840](NousResearch/hermes-agent#106840)), a clobbered first page is quarantined with its WAL instead of opened destructively ([#​106587](NousResearch/hermes-agent#106587)), WAL setup leaves an unverifiable database untouched ([#​106568](NousResearch/hermes-agent#106568)), and quarantined handles refuse VACUUM/FTS optimize ([#​106343](NousResearch/hermes-agent#106343), [#​106349](NousResearch/hermes-agent#106349)). Most of these salvaged community diagnoses by [@​kshitijk4poor](https://github.com/kshitijk4poor). ##### Multi-profile isolation hardening A cluster of fixes for installs running several profiles under one gateway (multiplex): secondary-profile bots no longer inherit the default profile's allow-lists ([#​107616](NousResearch/hermes-agent#107616)), adapters no longer send credentials to the default profile's host ([#​107617](NousResearch/hermes-agent#107617)), stdio MCP servers no longer receive the default profile's vault secrets ([#​107630](NousResearch/hermes-agent#107630)), `MEDIA:` delivery can no longer attach another profile's `.env` / `auth.json` / `state.db` ([#​107609](NousResearch/hermes-agent#107609)), Feishu drive callbacks and `/p/<profile>/` webhook replies stay on the routed profile ([#​107620](NousResearch/hermes-agent#107620), [#​107626](NousResearch/hermes-agent#107626)), and secondary profiles no longer get a sibling's Nous bearer from per-process memos ([#​107611](NousResearch/hermes-agent#107611)). ##### Desktop backend spawn storms are over Bot Mode used to spawn or dial one backend per profile on launch and on every roster tick, hovering the Bots roster spawned a backend per row, and profile switches could spawn a duplicate primary. ([#​108069](NousResearch/hermes-agent#108069), [#​108107](NousResearch/hermes-agent#108107), [#​108118](NousResearch/hermes-agent#108118), [#​108134](NousResearch/hermes-agent#108134), [#​107969](NousResearch/hermes-agent#107969), [#​108112](NousResearch/hermes-agent#108112) — salvage [#​102512](NousResearch/hermes-agent#102512), [#​103634](NousResearch/hermes-agent#103634), [#​103399](NousResearch/hermes-agent#103399), [#​107997](NousResearch/hermes-agent#107997) and others by [@​kshitijk4poor](https://github.com/kshitijk4poor)) ##### Password-blind credential vault The agent can now sign in, pay, and fill addresses from 1Password, Bitwarden, or the local Hermes vault without ever seeing a secret; two-factor codes come from a saved authenticator key or are asked for in the user's UI ([#​106480](NousResearch/hermes-agent#106480), [#​107585](NousResearch/hermes-agent#107585)). Private git plugins install with the user's stored credentials ([#​106981](NousResearch/hermes-agent#106981)). ##### Plugin catalog and one Plugins page A curated, SHA-pinned plugin index with CLI, admission CI, docs and dashboard ([#​69446](NousResearch/hermes-agent#69446)); Desktop gets one Plugins page owning agent + desktop plugins, install, catalog and per-commit pinning ([#​107212](NousResearch/hermes-agent#107212), [#​107314](NousResearch/hermes-agent#107314), [#​107321](NousResearch/hermes-agent#107321)); Radio ships as an opt-in SDK plugin ([#​107072](NousResearch/hermes-agent#107072)). ##### Nous free tier and guided first launch Free inference and connectors out of the box with one command to sign in ([#​105258](NousResearch/hermes-agent#105258), [#​105260](NousResearch/hermes-agent#105260)), `/login` from a chat ([#​105261](NousResearch/hermes-agent#105261)), connector tools (Gmail, Linear, Notion, ...) searchable through `tool_search` ([#​106842](NousResearch/hermes-agent#106842)), and a guided first launch behind `HERMES_GUEST_ONBOARDING=1` ([#​107697](NousResearch/hermes-agent#107697), [#​107958](NousResearch/hermes-agent#107958), [#​107985](NousResearch/hermes-agent#107985), [#​108211](NousResearch/hermes-agent#108211)). ##### 🐛 Notable Bug Fixes **Gateway & platforms** - A bare `display:` key in config.yaml no longer crashes every gateway turn ([#​106305](NousResearch/hermes-agent#106305)); a queued-lane final refused by the platform is recorded and redelivered ([#​106316](NousResearch/hermes-agent#106316)); a stalled WebSocket send no longer blocks every later event ([#​106581](NousResearch/hermes-agent#106581)); the first turn no longer waits on the Python toolchain probe ([#​106556](NousResearch/hermes-agent#106556)). - Telegram bots must @​mention when `bots_require_mention` is on, breaking bot-to-bot loops ([#​106534](NousResearch/hermes-agent#106534)); Matrix renders LaTeX ([#​106515](NousResearch/hermes-agent#106515)); Signal renders markdown tables ([#​106538](NousResearch/hermes-agent#106538)); WhatsApp replies to view-once messages keep their quote ([#​106541](NousResearch/hermes-agent#106541)); media-only replies report SUCCESS everywhere ([#​106557](NousResearch/hermes-agent#106557)). **Providers & routing** - `/model` and auxiliary auto never bill a provider you didn't select ([#​107366](NousResearch/hermes-agent#107366)); never auto-switch to a provider you have no credentials for ([#​107281](NousResearch/hermes-agent#107281)); Bedrock Claude/Converse/Mantle models survive `/model`, fallback and restore ([#​107621](NousResearch/hermes-agent#107621), [#​107658](NousResearch/hermes-agent#107658)); Bedrock Guardrails enforced ([#​107815](NousResearch/hermes-agent#107815)). - Codex: patch-budget image 400 shrinks and retries ([#​106525](NousResearch/hermes-agent#106525)); unentitled primary + fallback no longer oscillate ([#​106549](NousResearch/hermes-agent#106549)); Azure Foundry replayed-reasoning rejection classified and pruned ([#​106718](NousResearch/hermes-agent#106718) [@​erosika](https://github.com/erosika)). MCP OAuth refresh no longer erases the refresh token ([#​106185](NousResearch/hermes-agent#106185)). Anthropic clients send exactly one credential ([#​107978](NousResearch/hermes-agent#107978)). - DeepSeek V4.1 Flash on Nous Portal and OpenRouter pickers ([#​107489](NousResearch/hermes-agent#107489)); GPT Image 2.5 via OpenAI and FAL ([#​105988](NousResearch/hermes-agent#105988)); Opus 5 / Fable 5.1 on the native Anthropic picker ([#​106636](NousResearch/hermes-agent#106636) [@​xxxigm](https://github.com/xxxigm)). **Agent loop & compression** - One blocked periodic callback no longer stalls lease refresh ([#​106308](NousResearch/hermes-agent#106308)); a mid-turn `/steer` is persisted as its own user row ([#​106317](NousResearch/hermes-agent#106317), [#​106344](NousResearch/hermes-agent#106344)); local-inference memory-ceiling rejections back off instead of compressing history ([#​106307](NousResearch/hermes-agent#106307)); context-overflow after partial streaming ends the turn ([#​106567](NousResearch/hermes-agent#106567)); length continuation stops when the prompt filled the window ([#​106571](NousResearch/hermes-agent#106571)); compression no longer times out silently on aux retries ([#​106866](NousResearch/hermes-agent#106866)); `model_thresholds` keys can be provider-scoped ([#​108061](NousResearch/hermes-agent#108061)). - Surface switch (Desktop↔TUI) no longer rebuilds the system prompt and busts the prompt cache ([#​105844](NousResearch/hermes-agent#105844)); CLI keeps the `api_content` sidecar so the cache survives an early persist ([#​105842](NousResearch/hermes-agent#105842)). **CLI, TUI & Desktop** - `hermes -z --resume` continues the session ([#​106313](NousResearch/hermes-agent#106313)); Shift+letter and Cmd+Shift+Z work on extended-key terminals ([#​90674](NousResearch/hermes-agent#90674) [@​francip](https://github.com/francip), [#​105493](NousResearch/hermes-agent#105493)); `browser_exec` timeout kills the whole process tree ([#​106589](NousResearch/hermes-agent#106589)); update checks poll the GitHub API once a day instead of git-fetching every 30 min ([#​107648](NousResearch/hermes-agent#107648)); `hermes update` names the real cause and can't hang on a stalled fetch ([#​108053](NousResearch/hermes-agent#108053)). - Desktop: UI language survives the update relaunch ([#​106476](NousResearch/hermes-agent#106476)), expired OAuth grants get a one-click re-sign-in ([#​106965](NousResearch/hermes-agent#106965)), HUD mode shows the transcript again and always gives the window back ([#​107491](NousResearch/hermes-agent#107491), [#​107423](NousResearch/hermes-agent#107423)), the backend exits when its Desktop parent dies ([#​107977](NousResearch/hermes-agent#107977)), Windows updates stop reporting false failures ([#​106175](NousResearch/hermes-agent#106175), [#​107183](NousResearch/hermes-agent#107183)), WSLg renders on the Windows GPU ([#​106528](NousResearch/hermes-agent#106528)), Telegram quick setup with QR ported from the dashboard ([#​107242](NousResearch/hermes-agent#107242)), and \~60 more Desktop fixes largely from [@​OutThisLife](https://github.com/OutThisLife) and [@​kshitijk4poor](https://github.com/kshitijk4poor). **Cron & Kanban** - An off-tick "run now" no longer cancels the next scheduled run ([#​106306](NousResearch/hermes-agent#106306)); a killed manual run no longer blocks the next one for 5 minutes ([#​106733](NousResearch/hermes-agent#106733)); a one-shot changed to recurring keeps firing ([#​106532](NousResearch/hermes-agent#106532)); unpinned jobs run on their creation-snapshot model ([#​106499](NousResearch/hermes-agent#106499)); `--clone-all` no longer copies cron jobs ([#​106478](NousResearch/hermes-agent#106478)); `kanban promote` refuses undone parents ([#​106550](NousResearch/hermes-agent#106550)); `kanban_request_review` rejects unknown reviewer profiles ([#​106547](NousResearch/hermes-agent#106547)). **Tools & memory** - A stdio MCP server dying mid-call no longer replays the tool call ([#​106546](NousResearch/hermes-agent#106546)); a skills-only background review can no longer delete memory entries ([#​106310](NousResearch/hermes-agent#106310)); mem0 memory no longer drops long turns ([#​106542](NousResearch/hermes-agent#106542)); `tool_search` returns nothing rather than five tools sharing one word ([#​106676](NousResearch/hermes-agent#106676)); remote NOPASSWD sudo no longer prompts ([#​107939](NousResearch/hermes-agent#107939)); RSS and Reddit reading no longer activate by default ([#​105873](NousResearch/hermes-agent#105873)). **Housekeeping** - `config.yaml` backups live in one bounded `backups/config/` dir ([#​106388](NousResearch/hermes-agent#106388)); `hermes backup` keeps the newest 3 zips ([#​106455](NousResearch/hermes-agent#106455)); `hermes setup --reset` backs up the real config ([#​106453](NousResearch/hermes-agent#106453)); `debug share` retention shrunk to 1 day on the dpaste fallback ([#​106531](NousResearch/hermes-agent#106531)). ##### 👥 Contributors Thank you to the **140 contributors** whose commits, co-author trailers, and salvaged PRs landed in this window. **state.db campaign — salvaged PR authors:** [@​RikETS](https://github.com/RikETS), [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [@​Halldrix](https://github.com/Halldrix), [@​TaoMasterCoder](https://github.com/TaoMasterCoder), [@​chelsealong](https://github.com/chelsealong), [@​ca-shrimp](https://github.com/ca-shrimp), [@​gaoanze888](https://github.com/gaoanze888), [@​nikkoxgonzales](https://github.com/nikkoxgonzales), [@​QDung210](https://github.com/QDung210), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@​kshitijk4poor](https://github.com/kshitijk4poor), [@​jonpol01](https://github.com/jonpol01), [@​HexLab98](https://github.com/HexLab98), [@​Sora-bluesky](https://github.com/Sora-bluesky), [@​Xipong](https://github.com/Xipong), [@​efe-arv](https://github.com/efe-arv), [@​liuhao1024](https://github.com/liuhao1024), [@​mssteuer](https://github.com/mssteuer), [@​Mi55ed](https://github.com/Mi55ed), [@​SulthanZahran1](https://github.com/SulthanZahran1), [@​Finn763](https://github.com/Finn763), [@​nftpoetrist](https://github.com/nftpoetrist), [@​jangomango76](https://github.com/jangomango76), [@​leegunwoo98](https://github.com/leegunwoo98), [@​ggoldani](https://github.com/ggoldani). **state.db campaign — issue reporters** (the forensics in these threads were often better than the fixes): [@​thedigitalcarpenterdad](https://github.com/thedigitalcarpenterdad), [@​Rroven](https://github.com/Rroven), [@​aoeman84](https://github.com/aoeman84), [@​StephanRosin](https://github.com/StephanRosin), [@​rubensandrade-sketch](https://github.com/rubensandrade-sketch), [@​wanliqin](https://github.com/wanliqin), [@​chenzheshushi-commits](https://github.com/chenzheshushi-commits), [@​CarlosReyesPena](https://github.com/CarlosReyesPena), [@​revazone](https://github.com/revazone), [@​reservassai-art](https://github.com/reservassai-art), [@​Cuttingwater](https://github.com/Cuttingwater), [@​soroush5](https://github.com/soroush5), [@​e-shizz](https://github.com/e-shizz), [@​shobhit-87labs](https://github.com/shobhit-87labs), [@​shivanathd](https://github.com/shivanathd), [@​hoelzl](https://github.com/hoelzl), [@​i8ei](https://github.com/i8ei), [@​Ace-Kelly](https://github.com/Ace-Kelly), [@​YinsenWANG](https://github.com/YinsenWANG), [@​zbabiarz](https://github.com/zbabiarz), [@​Sravanjangam](https://github.com/Sravanjangam), [@​0gl20shk0sbt36](https://github.com/0gl20shk0sbt36), [@​RChina](https://github.com/RChina), [@​bronder](https://github.com/bronder), [@​ccwssy](https://github.com/ccwssy), [@​bottenbenny](https://github.com/bottenbenny), and [@​Hitman117890](https://github.com/Hitman117890) whose Discord report kicked the campaign off. **Everyone in the window (alphabetical):** [@​0genlab](https://github.com/0genlab), [@​0xalydev](https://github.com/0xalydev), [@​100yenadmin](https://github.com/100yenadmin), [@​1052326311](https://github.com/1052326311), [@​686f6c61](https://github.com/686f6c61), [@​69k4xmdfm2-blip](https://github.com/69k4xmdfm2-blip), [@​abundantbeing](https://github.com/abundantbeing), [@​Adolanium](https://github.com/Adolanium), [@​Ahmett101](https://github.com/Ahmett101), [@​albert748](https://github.com/albert748), [@​AlexxRussell](https://github.com/AlexxRussell), [@​alt-glitch](https://github.com/alt-glitch), [@​auroracapital](https://github.com/auroracapital), [@​austinpickett](https://github.com/austinpickett), [@​babatorik](https://github.com/babatorik), [@​Bartok9](https://github.com/Bartok9), [@​benbarclay](https://github.com/benbarclay), [@​bennybuoy](https://github.com/bennybuoy), [@​brian717](https://github.com/brian717), [@​briandevans](https://github.com/briandevans), [@​buihongduc132](https://github.com/buihongduc132), [@​ca-shrimp](https://github.com/ca-shrimp), [@​cervantesh](https://github.com/cervantesh), [@​Cesar-Azeredo](https://github.com/Cesar-Azeredo), [@​ChanPark03](https://github.com/ChanPark03), [@​chelsealong](https://github.com/chelsealong), [@​ckomma](https://github.com/ckomma), [@​ClintonEmok](https://github.com/ClintonEmok), [@​crazyief](https://github.com/crazyief), [@​ctaylor86](https://github.com/ctaylor86), [@​dalzio](https://github.com/dalzio), [@​DavidMetcalfe](https://github.com/DavidMetcalfe), [@​Drexuxux](https://github.com/Drexuxux), [@​edosulai](https://github.com/edosulai), [@​efe-arv](https://github.com/efe-arv), [@​emozilla](https://github.com/emozilla), [@​ericmaddox](https://github.com/ericmaddox), [@​erosika](https://github.com/erosika), [@​ethernet8023](https://github.com/ethernet8023), [@​everm1nd](https://github.com/everm1nd), [@​FalconOrtiz](https://github.com/FalconOrtiz), [@​fangliquanflq](https://github.com/fangliquanflq), [@​Finn763](https://github.com/Finn763), [@​FirmamentalSpring](https://github.com/FirmamentalSpring), [@​francip](https://github.com/francip), [@​g3org3yo](https://github.com/g3org3yo), [@​gaoanze888](https://github.com/gaoanze888), [@​ggoldani](https://github.com/ggoldani), [@​Halldrix](https://github.com/Halldrix), [@​haydster7](https://github.com/haydster7), [@​hbizi](https://github.com/hbizi), [@​helix4u](https://github.com/helix4u), [@​HexLab98](https://github.com/HexLab98), [@​huklaa](https://github.com/huklaa), [@​IAvecilla](https://github.com/IAvecilla), [@​infinitycrew39](https://github.com/infinitycrew39), [@​jahfaliabdulrahman-dev](https://github.com/jahfaliabdulrahman-dev), [@​jangomango76](https://github.com/jangomango76), [@​JoaoMarcos44](https://github.com/JoaoMarcos44), [@​jonpol01](https://github.com/jonpol01), [@​jwilson411](https://github.com/jwilson411), [@​KeyArgo](https://github.com/KeyArgo), [@​kokhlo](https://github.com/kokhlo), [@​KoNit-K](https://github.com/KoNit-K), [@​kshitijk4poor](https://github.com/kshitijk4poor), [@​kyssta-exe](https://github.com/kyssta-exe), [@​leegunwoo98](https://github.com/leegunwoo98), [@​lesterlxt](https://github.com/lesterlxt), [@​liuhao1024](https://github.com/liuhao1024), [@​Mabolla](https://github.com/Mabolla), [@​manuelschipper](https://github.com/manuelschipper), [@​MaxFreedomPollard](https://github.com/MaxFreedomPollard), [@​mearls0501](https://github.com/mearls0501), [@​mengyuyuan](https://github.com/mengyuyuan), [@​Mi55ed](https://github.com/Mi55ed), [@​MiseHinoha](https://github.com/MiseHinoha), [@​mjshorty](https://github.com/mjshorty), [@​mkrb84](https://github.com/mkrb84), [@​moisesvalero](https://github.com/moisesvalero), [@​moken627-hub](https://github.com/moken627-hub), [@​mssteuer](https://github.com/mssteuer), [@​nateEc](https://github.com/nateEc), [@​nftpoetrist](https://github.com/nftpoetrist), [@​nickseelert](https://github.com/nickseelert), [@​nikkoxgonzales](https://github.com/nikkoxgonzales), [@​notwitcheer](https://github.com/notwitcheer), [@​onuraycicek](https://github.com/onuraycicek), [@​outdog-hwh](https://github.com/outdog-hwh), [@​OutThisLife](https://github.com/OutThisLife), [@​philmossman](https://github.com/philmossman), [@​phuongvm](https://github.com/phuongvm), [@​pierrenode](https://github.com/pierrenode), [@​portavales](https://github.com/portavales), [@​PRATHAMESH75](https://github.com/PRATHAMESH75), [@​QDung210](https://github.com/QDung210), [@​rewbs](https://github.com/rewbs), [@​RikETS](https://github.com/RikETS), [@​romanovzky](https://github.com/romanovzky), [@​ryantuc](https://github.com/ryantuc), [@​Sahilvishnaliya](https://github.com/Sahilvishnaliya), [@​salch-cred](https://github.com/salch-cred), [@​sgarrand](https://github.com/sgarrand), [@​shannonsands](https://github.com/shannonsands), [@​simpolism](https://github.com/simpolism), [@​Solitud1nem](https://github.com/Solitud1nem), [@​somewheresy](https://github.com/somewheresy), [@​Sora-bluesky](https://github.com/Sora-bluesky), [@​sprmn24](https://github.com/sprmn24), [@​squevo](https://github.com/squevo), [@​StellarisW](https://github.com/StellarisW), [@​Stoltemberg](https://github.com/Stoltemberg), [@​SulthanZahran1](https://github.com/SulthanZahran1), [@​Svector-anu](https://github.com/Svector-anu), [@​szicely](https://github.com/szicely), [@​TaoMasterCoder](https://github.com/TaoMasterCoder), [@​teknium1](https://github.com/teknium1), [@​ten82e](https://github.com/ten82e), [@​thedavidweng](https://github.com/thedavidweng), [@​tkaufmann](https://github.com/tkaufmann), [@​Totoro-qaq](https://github.com/Totoro-qaq), [@​Tranquil-Flow](https://github.com/Tranquil-Flow), [@​tuancookiez-hub](https://github.com/tuancookiez-hub), [@​TurgutKural](https://github.com/TurgutKural), [@​ugoenyioha](https://github.com/ugoenyioha), [@​unsupportedpastels](https://github.com/unsupportedpastels), [@​victor-kyriazakos](https://github.com/victor-kyriazakos), [@​webtecnica](https://github.com/webtecnica), [@​wliu-dev](https://github.com/wliu-dev), [@​wukangcheng1994](https://github.com/wukangcheng1994), [@​Xipong](https://github.com/Xipong), [@​Xixiartemis](https://github.com/Xixiartemis), [@​xkam7ar](https://github.com/xkam7ar), [@​xxxigm](https://github.com/xxxigm), [@​yavarb](https://github.com/yavarb), [@​yoniebans](https://github.com/yoniebans), [@​Youssef](https://github.com/Youssef), [@​yoyodine-industries](https://github.com/yoyodine-industries), [@​yuanchenglu](https://github.com/yuanchenglu), [@​YuhGuan](https://github.com/YuhGuan), [@​Zeus-Deus](https://github.com/Zeus-Deus). Also: Youssef. ##### Updating - Existing install: `hermes update` - Fresh install: `curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash` - Managed deployments should update through their deployment tooling using the new tag. - If your `state.db` was already damaged by 0.21.0/0.21.1: run `hermes doctor` first; it now names structural vs index damage correctly and points at `hermes sessions recover --inspect-only` (profile-pinned) when a rebuild isn't enough. **Full Changelog:** [v2026.9.7...v2026.9.11](NousResearch/hermes-agent@v2026.9.7...v2026.9.11) </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:eyJjcmVhdGVkSW5WZXIiOiI0NC42NS4wIiwidXBkYXRlZEluVmVyIjoiNDQuNjUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUvY29udGFpbmVyIiwidHlwZS9wYXRjaCJdfQ==--> Reviewed-on: https://git.xcd.dev/gabrielcosi/home-ops/pulls/754
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.
Opening
state.dbno longer takes SQLite's write lock when the file needs no writes, so a freshhermesprocess is not stalled behind a sibling process holding a transaction.Based on #101881 by @jonpol01 (cherry-picked, authorship preserved).
Root cause
Every read-write open ran three unconditional write statements plus an unconditional drop+recreate of four triggers — the
db_file_generationstamp (INSERT OR IGNORE), theactive IS NULLheal (UPDATE … WHERE active IS NULL) and thefts_storage_versionmarker upsert. SQLite takes the RESERVED lock for a write statement even when it matches zero rows, so on a settled database each open blocked for the full busy timeout whenever another process heldBEGIN IMMEDIATE.Changes
hermes_state.py::_ensure_db_file_generation— read the stamp first; insert only when absent (first opener still wins, re-read after insert).hermes_state_schema.py— theactiveheal runs only when a NULL row exists; the FTS layout marker is written only when its value differs.hermes_state_schema.py— the four display triggers inDEFERRED_INDEX_SQLwere dropped and re-created on every open (DROP TRIGGER IF EXISTSon an existing trigger takes the write lock; theCREATE … IF NOT EXISTSforms do not); each pair now runs only when the stored trigger body differs from the desired one, so a changed trigger still rolls out. Found by review: without this the open still blocked and the lock test could not pass.tests/test_hermes_state.py— trimmed from the PR's 71 lines to two invariants: a settled open issues noINSERT/UPDATE/DELETEagainst the main tables; an open completes while a sibling process holds the write lock. Both proven RED with the gates reverted (statement gates AND the trigger gate), GREEN restored.Comment on the stamp path tightened to the why.
Measured impact
Temp
HERMES_HOME, database settled by two prior opens, sibling subprocess holdingBEGIN IMMEDIATEfor 3 s, 5 runs each:SessionDB(path)open under a sibling write lock (median)INSERT/UPDATE/DELETE/DROP TRIGGER)temp.FTS5 capability probe remains)CREATE TABLE IF NOT EXISTSstatements are unchanged (SQLite resolves them against the schema before taking the lock). Read-only opens and the_fts_stalepath are untouched.Pre-existing on this host (fails identically on a clean
origin/main, environmental):TestPerformancePragmasEndToEnd::test_configured_pragmas_reach_all_connection_types— linked SQLite 3.46 falls back tojournal_mode=DELETEunder the WAL-reset-bug guard.