Skip to content

fix(state): the sqlite3 salvage remedy must survive the shell when pasted - #891

Merged
Kyzcreig merged 1 commit into
mainfrom
daedalus-opus/t_b649d6d1-state-sqlite-hint
Sep 23, 2026
Merged

Kyzcreig merged 1 commit into
mainfrom
daedalus-opus/t_b649d6d1-state-sqlite-hint

Conversation

@Kyzcreig

@Kyzcreig Kyzcreig commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Argus FINDING F3 from the round-2 review of #889, carded as t_b649d6d1. Non-blocking there and deliberately carded as an orthogonal file.

Stacked on #889 (daedalus-opus/t_c9e1a012-hint-shell) — it needs hermes_cli/cli_hint.hint_value, which #889 introduces. Retarget to main once #889 lands.

The bug

hermes_state printed a pasteable recovery command with the DB path interpolated bare, at three sites on the DB-corruption recovery path:

... salvage with `sqlite3 {db_path} ".recover"`.

For a HERMES_HOME containing a space the printed remedy splits:

printed  : sqlite3 /Users/x/My Drive/hermes/state.db ".recover"
bash argv: ['sqlite3', '/Users/x/My', 'Drive/hermes/state.db', '.recover']
sqlite3  : exit=1  Error: near "Drive": syntax error

Reachable: HERMES_HOME is an arbitrary user-set env var, Google Drive mounts as My Drive on macOS, and on Windows the platform default is LOCALAPPDATA/hermes under C:/Users/<First Last>/ where a space is the norm. Unreachable remedy on the one path where the operator has least slack.

E2E through the real hermes sessions repair path, #889's fixed --source spelling printed directly beneath the unfixed one:

| ✗ Repair failed: ... salvage with `sqlite3 /tmp/.../My Drive/hermes/state.db ".recover"`.
|   Next step — offline recovery (never modifies the source):
|     hermes sessions recover '--source=/tmp/.../My Drive/hermes/state.db.bak' \

The fix

All three sites routed through hermes_cli.cli_hint.hint_value — the choke point #889 landed. 4 lines plus one import.

site function
hermes_state.py:2521 _persistent_repair_exhausted_error (repair budget exhausted)
hermes_state.py:2721 _backup_db_file, low-disk guard
hermes_state.py:2736 _backup_db_file, disk-space-unknown guard

No import cycle: hermes_state.py already hard-imports hermes_cli.sqlite_runtime and hermes_cli.config at module level, and cli_hint's only dependency is shlex. Verified by import, not argued.

Verification

Reproduced before fixing — Argus's argus_r2_e2e_state.py on this tree: db path survives as ONE word: NO, real sqlite3 exit=1. After: YES.

7 new tests (tests/test_state_db_recover_hint_pasteable.py) drive the REAL print path — sessions_cmd.cmd_sessions with a real damaged DB under a real spaced HERMES_HOME — extract the backticked span from captured stdout, and hand it to a real /bin/bash. The paste is not simulated with shlex; that was exactly the F2 tautology round 1 of the parent card was blocked for. Includes an over-fix guard (an ordinary path must not grow quotes) and a source-shape class guard.

Mutation battery — each mutation applied by a mutator that refuses to report a no-op, each reverted and cmp-verified byte-identical:

mutation result
M1 revert all 3 sites 5 failed
M2 revert site 1 only 3 failed
M3 revert site 2 only 2 failed
M4 revert site 3 only 2 failed
M5 hint_value → passthrough 4 failed

Each single-site mutation reds exactly its own test plus the class guard — the tests discriminate per-site, not as a lump.

Adjacency, same 8 repair/hint files on BOTH trees: 210 passed at #889's head, 210 passed here. Identical.

ruff 0.15.10 clean on both changed files; git diff --check clean.

Class sweep

Re-ran Argus's argus_r2_pasteable.py: 75 → 72 sites, and the diff is exactly my three. That probe mechanises only half of the discriminator (a value inside a backticked pasteable span); the other half — the value can hold a shell metacharacter — I mechanised too, partitioning the residual 72 by value provenance rather than asserting dismissals:

  • 57 OUT: structurally constrained identifiers — plugin/skill ids normalised to [a-z0-9_-], provider names from a fixed catalogue, board slugs gated by _BOARD_SLUG_RE in kanban_db.py:899, generated cron job ids, git refnames (git forbids whitespace).
  • 12 IN + 3 hand-adjudicated: real paths or free-form text.

I then measured the IN-class groups through their real printers rather than trusting the classifier — 6 of 6 break a real paste, including the two the card flagged:

site measured
tools/self_repo_guard.py:733 {scratch} — #889 fixed root in this span but scratch is _scratch_dir_hint() = the same HERMES_HOME, in the same printed command. Splits.
whatsapp/adapter.py:612+626 cd {bridge_dir} && {_npm_bin} install — bash refuses outright
runtime-parity-check.py:482 git -C {TREE} … splits
doctor.py:835, gateway/run.py:28345 {sys.executable} splits
gateway/run.py:4265 hermes skills install {install_path} splits
plugins_cmd.py:1106+2855 {recorded_source} — bash refuses outright

plugins_cmd.py:3019 (which the card flagged) is OUT: the span is a literal `git stash pop` with no interpolation; {target} sits in the surrounding prose, not inside the backticks.

These are out of this card's scope (different files and subsystems) and are left for a follow-up — I did not widen the diff. self_repo_guard:733 is the sharpest of them: it is a half-fixed span in a file #889 already touched.

Fork-first per the fleet default. hermes_state.py exists upstream in NousResearch/main; the upstream PR follows once #889 lands, and this does not wait on it.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@Kyzcreig

Copy link
Copy Markdown
Collaborator Author

CI note: slice 16/16 is RED, and it is INHERITED from #889 — not this PR

tests/cli/test_exit_summary_resume_hint.py::TestExitSummaryResumeHint::test_resume_hint_includes_profile_flag_on_title_hint_too

>       assert 'hermes -c "My Cool Session" -p dev' in out
E       assert ... in "\n  hermes -c 'My Cool Session' -p dev\n..."

Measured, not argued:

  • clean worktree at fix(cli): a printed hint must survive a real SHELL, not just shlex.split #889's head 7327d224ae with this PR's diff absent -> 1 failed, 4 passed
  • this branch -> 1 failed, 4 passed, same test, same assertion
  • git diff --name-only refs/remotes/pr889..HEAD = hermes_state.py, tests/test_state_db_recover_hint_pasteable.py — touches neither cli.py nor that test file
  • gh pr checks 889 is red on the same slice

Root cause: cli.py:17984 (a #889 site) now prints hint_value(session_title) -> 'My Cool Session', the POSIX single-quoted form shlex.quote produces. The pre-existing test still asserts the old hand-rolled double-quoted spelling — the exact spelling #889 exists to eliminate, since double quotes don't stop $VAR/`cmd` expansion. The test needs its expectation updated; the site is correct.

Incidentally this disproves the "cli.py:17984 is fixed but NOT GATED" finding carried on the parent card: it is gated, just by tests/cli/test_exit_summary_resume_hint.py in a different slice rather than by tests/hermes_cli/test_cli_hint.py (the only file the M8 mutation re-ran). No live-CLI harness is needed.

Filed as t_0c5ac29a against #889. Not touching it from here — different file, outside this card's contract.

The 37 other checks pass, including ruff enforcement, ruff + ty diff, Windows footguns, macOS-only tests and e2e.

@Kyzcreig

Copy link
Copy Markdown
Collaborator Author

Upstream port opened: NousResearch#119604 (up/state-repair-hint-pasteable, head 8e64aab).

NOT a cherry-pick — re-measured against upstream/main @ ade48144 today:

The CLASS does, and it is measured: upstream's replacement command still interpolates the path bare — hermes {profile_arg}sessions recover --source {db_path} ... at 3 sites / 4 printed spans. With a spaced HERMES_HOME a real /bin/bash splits all 4 and the real sessions recover subparser exits 2. Clean main: 9/9 hostile cases red. Ported branch: 14/14 green. Adjacency A/B identical on both trees (4 failed / 1447 passed / 1 error, same node IDs — inherited).

Upstream is pull-only for us; this PR is not a blocker on #891.

…sted

hermes_state printed a pasteable recovery command with the DB path
interpolated bare at three sites on the DB-corruption path:

    ... salvage with `sqlite3 {db_path} ".recover"`.

For a HERMES_HOME holding a space (Google Drive's "My Drive", or the Windows
LOCALAPPDATA default under C:/Users/<First Last>/, where a space is the NORM)
the printed remedy splits:

    printed  : sqlite3 /Users/x/My Drive/hermes/state.db ".recover"
    bash argv: ['sqlite3', '/Users/x/My', 'Drive/hermes/state.db', '.recover']
    sqlite3  : exit=1  Error: near "Drive": syntax error

Unreachable remedy on the one path where the operator has least slack. Routed
all three through hermes_cli.cli_hint.hint_value, the choke point #889 landed;
no import cycle (hermes_state.py already hard-imports hermes_cli.sqlite_runtime
at module level, and cli_hint's only dependency is shlex).

  hermes_state.py:2521  _persistent_repair_exhausted_error
  hermes_state.py:2721  _backup_db_file, low-disk guard
  hermes_state.py:2736  _backup_db_file, disk-space-unknown guard

Found by Argus as FINDING F3 in the round-2 review of PR #889 (card
t_b649d6d1), non-blocking and deliberately carded as an orthogonal file.

VERIFIED
- Reproduced first, E2E through the real `hermes sessions repair` path with a
  real damaged DB under a spaced HERMES_HOME: broken before, one word after.
- 7 new tests drive the REAL print path (sessions_cmd.cmd_sessions), extract
  the backticked span from CAPTURED stdout and hand it to a REAL /bin/bash.
  No shlex simulation of the paste — that was the F2 tautology round 1 of the
  parent card was blocked for. 7 passed.
- Mutation battery, each applied by a mutator that REFUSES to report a no-op,
  each reverted and cmp-verified byte-identical:
    M1 revert all 3 sites          -> 5 failed
    M2 revert site 1 only          -> 3 failed
    M3 revert site 2 only          -> 2 failed
    M4 revert site 3 only          -> 2 failed
    M5 hint_value -> passthrough   -> 4 failed
  Each single-site mutation reds exactly its own test plus the class guard.
- Adjacency, same 8 files on BOTH trees: 210 passed at PR-889 base, 210 passed
  here. Identical.
- ruff 0.15.10 clean; git diff --check clean.

Implementation diff is 4 lines plus one import.
@Kyzcreig
Kyzcreig force-pushed the daedalus-opus/t_b649d6d1-state-sqlite-hint branch from a9cc3fa to 1882e84 Compare September 23, 2026 01:46
Kyzcreig added a commit that referenced this pull request Sep 23, 2026
…hell

Residual of the #889/#891 unreachable-remedy class. Eight sites across six
subsystems interpolated a HERMES_HOME-derived path -- or an interpreter or
executable path resolved under one -- bare into a backticked span the operator
is told to paste. For a home holding a space (Google Drive's "My Drive", or
the Windows C:/Users/<First Last> default where a space is the NORM) the
printed remedy does something other than what the message says.

Measured before the fix, each through its REAL printer under a real spaced
HERMES_HOME, with the printed span handed to a REAL /bin/bash:

  self_repo_guard   git clone --shared '<root>' <scratch>/t_123
                    -> ['git','clone','--shared','<root>',
                        '/tmp/.../My','Drive/hermes/scratch/t_123']
                    #889 fixed {root} and left {scratch} bare in the SAME span.

  whatsapp adapter  cd <bridge> && <npm> install
                    -> bash rc=127: /tmp/.../My: No such file or directory
                    (refuses outright; the remedy cannot even start)

  runtime-parity    git -C <TREE> log --oneline HEAD...fork/main
                    -> ['git','-C','/tmp/.../My','Drive/.../hermes-agent', ...]

  doctor / run.py   <sys.executable> -m pip install ...
                    -> ['/tmp/.../My','Drive/venv/bin/python','-m','pip', ...]

  run.py skills     hermes skills install official/My Category/demo-skill
                    -> [...,'official/My','Category/demo-skill']

  plugins_cmd x2    hermes plugins install <source> --force --ref <sha>
                    -> [...,'file:///tmp/.../My','Drive/.../demo-plugin', ...]

Routed each through hermes_cli.cli_hint.hint_value, the choke point #889
established. Import direction was checked per file; five import hermes_cli
directly. staging/scripts/runtime-parity-check.py cannot: it deploys STANDALONE
to ~/.hermes/scripts/ and runs under /usr/bin/python3, where hermes_cli is not
importable (verified), so it carries a verbatim local copy with a test pinning
it to the shared implementation over six token shapes.

TESTS drive the REAL print path and judge with a REAL shell; no assertion is on
the string's shape, and the paste is never simulated with shlex (the tautology
the grandparent card was blocked for). The whatsapp span is a COMPOUND command
(`cd X && Y install`), not a word list, so printf would execute rather than lex
it -- that oracle instead RUNS the span and has the callee report the argv and
cwd bash actually handed it.

Verified:
  tests/test_residual_cli_hint_pasteable.py       12 passed
  mutation: 10 mutants, 10 KILLED -- each of the 8 sites reverted individually
    reds its OWN assertion; M10 (always-quote) reds the three readable-form
    guards, proving the over-fix control bites
  adjacency: 468 passed across test_self_repo_guard, test_terminal_task_cwd,
    test_plugins_cmd, test_plugin_install_ref, test_doctor, test_voice_command,
    test_cli_hint, test_whatsapp_send_message_media
  staging/tests/test_pending_restart_redrive.py green under /usr/bin/python3,
    confirming the standalone shim does not break the real deploy path
  ruff 0.15.20 clean; git diff --check clean

Stacked on #889 (hint_value lands there); do not merge before it.

Card: t_e587758d
Kyzcreig added a commit that referenced this pull request Sep 23, 2026
…hell

Residual of the #889/#891 unreachable-remedy class. Eight sites across six
subsystems interpolated a HERMES_HOME-derived path -- or an interpreter or
executable path resolved under one -- bare into a backticked span the operator
is told to paste. For a home holding a space (Google Drive's "My Drive", or
the Windows C:/Users/<First Last> default where a space is the NORM) the
printed remedy does something other than what the message says.

Measured before the fix, each through its REAL printer under a real spaced
HERMES_HOME, with the printed span handed to a REAL /bin/bash:

  self_repo_guard   git clone --shared '<root>' <scratch>/t_123
                    -> ['git','clone','--shared','<root>',
                        '/tmp/.../My','Drive/hermes/scratch/t_123']
                    #889 fixed {root} and left {scratch} bare in the SAME span.

  whatsapp adapter  cd <bridge> && <npm> install
                    -> bash rc=127: /tmp/.../My: No such file or directory
                    (refuses outright; the remedy cannot even start)

  runtime-parity    git -C <TREE> log --oneline HEAD...fork/main
                    -> ['git','-C','/tmp/.../My','Drive/.../hermes-agent', ...]

  doctor / run.py   <sys.executable> -m pip install ...
                    -> ['/tmp/.../My','Drive/venv/bin/python','-m','pip', ...]

  run.py skills     hermes skills install official/My Category/demo-skill
                    -> [...,'official/My','Category/demo-skill']

  plugins_cmd x2    hermes plugins install <source> --force --ref <sha>
                    -> [...,'file:///tmp/.../My','Drive/.../demo-plugin', ...]

Routed each through hermes_cli.cli_hint.hint_value, the choke point #889
established. Import direction was checked per file; five import hermes_cli
directly. staging/scripts/runtime-parity-check.py cannot: it deploys STANDALONE
to ~/.hermes/scripts/ and runs under /usr/bin/python3, where hermes_cli is not
importable (verified), so it carries a verbatim local copy with a test pinning
it to the shared implementation over six token shapes.

TESTS drive the REAL print path and judge with a REAL shell; no assertion is on
the string's shape, and the paste is never simulated with shlex (the tautology
the grandparent card was blocked for). The whatsapp span is a COMPOUND command
(`cd X && Y install`), not a word list, so printf would execute rather than lex
it -- that oracle instead RUNS the span and has the callee report the argv and
cwd bash actually handed it.

Verified:
  tests/test_residual_cli_hint_pasteable.py       12 passed
  mutation: 10 mutants, 10 KILLED -- each of the 8 sites reverted individually
    reds its OWN assertion; M10 (always-quote) reds the three readable-form
    guards, proving the over-fix control bites
  adjacency: 468 passed across test_self_repo_guard, test_terminal_task_cwd,
    test_plugins_cmd, test_plugin_install_ref, test_doctor, test_voice_command,
    test_cli_hint, test_whatsapp_send_message_media
  staging/tests/test_pending_restart_redrive.py green under /usr/bin/python3,
    confirming the standalone shim does not break the real deploy path
  ruff 0.15.20 clean; git diff --check clean

Stacked on #889 (hint_value lands there); do not merge before it.

Card: t_e587758d
@Kyzcreig

Copy link
Copy Markdown
Collaborator Author

🤖 merged-by: apollo · lane: landing-stack · gate: BYPASS: FleetReview is DISABLED by operator (Ace, 09:30 PT 'Fleet reviews currently paused'); landing on kanban APPROVAL + green CI. Ace 18:17 PT: 'landing stack 888 and 891, handle that right now'. · why: t_b649d6d1 APPROVED r1 ARTIFACT (sqlite3 .recover remedy word-splits on a HERMES_HOME with a space; 3 sites in hermes_state.py). Was stacked on #889 (now merged); retargeted to main, CI green on the new base. Upstream twin: NousResearch#119604 (t_d19cb3bc).

@Kyzcreig
Kyzcreig added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit fab0f69 Sep 23, 2026
54 checks passed
@Kyzcreig
Kyzcreig deleted the daedalus-opus/t_b649d6d1-state-sqlite-hint branch September 23, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant