fix(state): keep unrelated processes from blocking FTS recovery - #92419
fangliquanflq wants to merge 3 commits into
Conversation
Reviewed What's good
Suggestions
Strong, well-tested precision fix; #1 is a documentation/comment ask, not a blocker. |
|
Thanks for the detailed cross-check. I confirmed both points against the current tip:
No code change is needed for these points. The current required Python test job and aggregate required-check gate are passing. |
lEWFkRAD
left a comment
There was a problem hiding this comment.
Reviewed on Windows: merged the PR head (5ec422e) onto the current main tip in a clean worktree and ran the state suite.
tests/state/: 102 passed, 6 skipped, 2 failed. Both failures are Windows-only environment artifacts, not PR defects:test_foreign_holder_detection_proc_readlink_deleted_wal— Windowsos.readlinkreturns\\?\extended-length paths, so the canonical-path match can never fire and the assertion fails vacuously. Reproduced on plain main. Proposed as #96475 (draft).test_foreign_holder_uninspectable_process_cmdline_fallback— the test fakes a Linux/proctree (includingchmod 000to simulate an unreadable fd dir), but production_foreign_state_db_holdersbails to[]/unknown-holder before any of that when_IS_WINDOWS, and on Windows git-bashchmod 000doesn't make the dir unreadable in the first place. Theassert holders[0][0] == 222gets-1("scan unavailable"). Pre-existing on main; the PR didn't introduce it but also doesn't fix it — worth askipiffollow-up from someone with Linux CI in the loop.
- On Linux/CI where these paths actually execute, the suite is clean and the premise holds: the holder-detection change is sound, the narrow-identity and clustered-option commits read correctly against main.
Verdict: approve on the merits, conditional on CI green on Linux runners. Not submitting a formal APPROVE review from this account so the maintainer call stays yours.
|
Integration update: #97330 now carries the argv-aware contribution in its single current- The original contribution is preserved through the exact trailer The rematerialized exact head passes the focused 55-test runner and the 186-test broad repair/WAL/FTS suite; Ruff, byte compilation, Windows footgun scan, and |
Superseded by #97330, which preserves and integrates this argv-aware holder-detection contribution alongside the complementary inode-based repair guard. |
… HERMES_HOME Field evidence (2026-09-07, production host): the host runs two independent Hermes instances - a main gateway (user ubuntu, HERMES_HOME=/home/ubuntu/.hermes) and a demo gateway (user demo, HERMES_HOME=/home/demo/.hermes). Because the demo process is owned by another user, its /proc/<pid>/fd table is unreadable and foreign_state_db_holders() falls back to cmdline + _looks_like_hermes(). The demo argv matches Hermes patterns exactly, so it was flagged as an uninspectable holder of the MAIN instance's state.db even though lsof proved 0 open handles on it. Result: _recover_stale_fts was deferred 42 times across 6 gateway restarts, the fts_stale breadcrumb never cleared, and FTS self-repair stayed permanently disabled. NousResearch#92419 removed substring false positives (journalctl/grep mentioning hermes); a genuine second instance with a DIFFERENT HERMES_HOME was still misjudged. Add _argv_scoped_to_other_home(): when the argv of an uninspectable Hermes process proves it lives under a different /.hermes home (or a state.db sidecar under a different parent) AND no token references our state.db, sidecars, or home, do not count it as our holder. Applied to all three uninspectable branches (holder + two descriptor paths). Ambiguous argv without absolute-path tokens remains fail-closed, preserving the conservative intent. References NousResearch#92401
… HERMES_HOME Field evidence (2026-09-07, production host): the host runs two independent Hermes instances - a main gateway (user ubuntu, HERMES_HOME=/home/ubuntu/.hermes) and a demo gateway (user demo, HERMES_HOME=/home/demo/.hermes). Because the demo process is owned by another user, its /proc/<pid>/fd table is unreadable and foreign_state_db_holders() falls back to cmdline + _looks_like_hermes(). The demo argv matches Hermes patterns exactly, so it was flagged as an uninspectable holder of the MAIN instance's state.db even though lsof proved 0 open handles on it. Result: _recover_stale_fts was deferred 42 times across 6 gateway restarts, the fts_stale breadcrumb never cleared, and FTS self-repair stayed permanently disabled. #92419 removed substring false positives (journalctl/grep mentioning hermes); a genuine second instance with a DIFFERENT HERMES_HOME was still misjudged. Add _argv_scoped_to_other_home(): when the argv of an uninspectable Hermes process proves it lives under a different /.hermes home (or a state.db sidecar under a different parent) AND no token references our state.db, sidecars, or home, do not count it as our holder. Applied to all three uninspectable branches (holder + two descriptor paths). Ambiguous argv without absolute-path tokens remains fail-closed, preserving the conservative intent. References #92401
What does this PR do?
Unrelated root-owned wrappers and log readers can no longer prevent a stale FTS index from recovering merely because an argument mentions Hermes. The fallback now preserves
/proc/<pid>/cmdlineargument boundaries and recognizes Hermes only from the executable or an exact Python module/script argument, while the open-file holder check remains unchanged.Symptom
When another process has an unreadable
/proc/<pid>/fddirectory and its command arguments contain text such ashermes-agent, Hermes treats it as a possiblestate.dbholder. Runtime and startup FTS repair are then deferred even though that process has no descriptor open on the database.Impact
A corrupted FTS index can remain stale while unrelated SSH wrappers,
journalctl,grep, or terminal commands are running. Session search silently stays on the slowerLIKEfallback instead of restoring FTS search.Bug Cause
Trigger:
hermes_state.py:4783/_foreign_state_db_holders()/ unreadable foreign fd tableCausal chain:
_looks_like_hermes()searches that entire string for broad Hermes substrings, including ordinary data arguments.Why it is wrong: Mentioning Hermes in an argument does not identify the process that could own a SessionDB connection.
Working sibling / contrast: Processes with readable fd tables are matched against the exact
state.db, WAL, and SHM targets and do not use the command-line heuristic.Ruled out: The descriptor-based holder guard is not the source of the false positive; it only reports watched database paths and remains unchanged.
Fix
Preserve argv boundaries when reading
/proc/<pid>/cmdline. Treat a process as Hermes only when its executable basename is a Hermes binary, or when a Python/PyPy interpreter has an exacthermes_cli.mainmodule orhermes_cli/main.pyscript argument. Regression cases cover unrelated wrappers and real Hermes launch forms.Related Issue
Fixes #92401
Type of Change
Changes Made
hermes_state.py- parse/proccommand lines as argv and identify Hermes from executable positions instead of substrings.tests/state/test_fts_runtime_rebuild.py- cover false-positive wrappers and supported Hermes binary, module, and script launches.How to Test
scripts/run_tests.sh tests/state/test_fts_runtime_rebuild.py -k 'uninspectable_non_hermes_process_is_not_a_holder or uninspectable_hermes_process_remains_a_holder' -q9 passed.scripts/run_tests.sh tests/state/test_fts_runtime_rebuild.py -qto exercise the/procholder path with POSIX path semantics.Checklist
Code
fix(scope):,feat(scope):, etc.)/procverification is included in the review handoffDocumentation & Housekeeping
cli-config.yaml.exampleis N/A; no config keys changedCONTRIBUTING.mdandAGENTS.mdare N/A; no architecture or workflow changed