Repository navigation
Conversation
…path
`_persistent_repair_exhausted_error` and both `_backup_free_space_error`
refusals print a backticked `hermes sessions recover --source <db> ...`
span and tell the operator to paste it. The path was interpolated bare,
so a HERMES_HOME containing whitespace printed a remedy that does not
parse:
printed : hermes sessions recover --source /Users/x/My Drive/hermes/state.db --inspect-only
bash argv: ['hermes','sessions','recover','--source','/Users/x/My',
'Drive/hermes/state.db','--inspect-only']
argparse : exit 2
Reachable on ordinary installs: HERMES_HOME is user-set, Google Drive
mounts as "My Drive" on macOS, and the Windows default sits under
C:/Users/<First Last>. The failure lands on the one path where the
operator has least slack -- automatic repair has already given up.
Adds `hermes_cli/cli_hint.hint_arg`, the single place that knows the two
escaping rules (shell re-lexing; argparse binding a `-`-leading value as
an option), and routes the three sites through `_source_arg`. Ordinary
paths keep the plain readable `--source <db>` spelling; only a path that
`shlex.quote` would change gets the quoted attached `'--source=<db>'`
form, which is the only spelling argparse accepts for a value beginning
with `-`. Installs without hermes_cli fall back to the same attached form.
Verified with a real /bin/bash and the real `sessions recover` argparse
subparser, not a shlex simulation: the printed span is extracted from
what the real builders returned, bash produces the words, and the real
parser must bind the exact path to --source. On clean main 9/9 hostile
cases fail (argparse exit 2); with this change 14/14 pass, including
over-fix controls pinning that ordinary paths gain no quotes.
…the predicate
The PR's `test_hint_arg_is_the_choke_point` asserts
`_source_arg(p) == hint_arg("--source", p)`. Both sides call the same
`hint_arg`, so the assertion holds BY CONSTRUCTION when the predicate itself
changes: it pins DELEGATION, not correctness. Measured -- narrowing
`_is_shell_literal` to the `shlex.split` under-approximation its own docstring
warns against leaves all 14 of the PR's tests green while a real /bin/bash
expands `$HOME`, EXECUTES `` `id` `` and `$(id)`, brace-expands `{a,b}` and
refuses `a;b` outright.
Adds `tests/hermes_cli/test_cli_hint.py` -- the owner suite for the helper,
driven through a REAL bash into a real argparse parser (a `shlex`-based paste
simulator would be the same function on both sides of the assertion).
Verified on this tree (repo venv, python 3.11.15):
unmutated : 41 passed (guard) / 55 passed (guard + PR suite)
_is_shell_literal -> shlex.split mutant:
new owner suite : 20 failed, 21 passed <- kills it
PR's own suite, same tree : 14 passed <- the gap
always-quote (over-fix) mutant:
new owner suite : 7 failed <- readable form guarded
_source_arg re-derives (bypass chokepoint) mutant:
PR's own suite : 11 failed <- still owns delegation
new owner suite : 41 passed <- complementary, not redundant
guard kills the predicate mutant with hermes_state_repair.py REMOVED
(20 failed) -- it fires from hint_arg's own surface, not via the call site.
Adjacency: 68 passed across the hint-adjacent modules.
ruff 0.15.20 and 0.16.2 clean; git diff --check clean.
Both implementation files restored byte-identical after every mutant
(cli_hint.py 71590f6c..., hermes_state_repair.py 05c05afe...).
No implementation file changed; this commit is test-only.
|
Follow-up from our own round-2 review of this PR: the port brought The gap, measured. leaves all 14 tests on this branch green, while a real What the commit adds. Numbers on this tree (repo venv, python 3.11.15,
The last row is the point: the two suites are complementary, not redundant. The existing chokepoint test owns delegation and kills the bypass mutant; the new suite owns the predicate. Neither covers the other. The new suite also kills the predicate mutant (20 failed) with Adjacency: 68 passed across the hint-adjacent modules. Deliberately not included: |
The printed recovery command does not parse when pasted
hermes_state_repairprints a copy-pasteable salvage command at three sites on theDB-corruption path, and interpolates the database path bare into the backticked span:
_persistent_repair_exhausted_error— two spans (--inspect-only,--output)_backup_free_space_error—_MANUAL_RECOVER_HINT, returned to the operator by_backup_db_fileon both refusal branches (no headroom / unstattable volume)With a
HERMES_HOMEcontaining whitespace, the shell re-lexes the printed text beforeargparse ever sees it. Measured on
main@ade48144with a real/bin/bashand the realsessions recoversubparser:All 4 printed spans split; the parser refuses every one.
This is reachable on ordinary installs, not a contrived path:
HERMES_HOMEis an arbitrary user-set environment variableMy Driveon macOSC:/Users/<First Last>, where a space is the normAnd it lands on the one path where the operator has least slack: automatic repair has
already exhausted its budget, so the printed remedy is the remaining instruction — and it
is unreachable as printed.
The fix
hermes_cli/cli_hint.py(new, ~30 lines, stdlibshlexonly) is the single place thatknows the two escaping rules:
;&|()are controloperators,
{a,b}brace-expands,*?[]glob against the operator's CWD, and$VAR/`cmd`/$(cmd)/~expand or execute;-as an option and refuses withexpected one argument.hint_arg(flag, value)returns the plain--flag valueform whenshlex.quoteleaves thetoken unchanged — so ordinary paths keep the readable spelling and every existing message
is byte-identical. Only a path that actually needs escaping gets the quoted attached
'--source=<path>'form, which is the only spelling argparse accepts for a value beginningwith
-.hermes_state_repair._source_argroutes the three sites through it, with analways-quoted-attached fallback for scaffold/embed installs that have no
hermes_cli(the module already guards its other
hermes_cliimports the same way).I deliberately kept this to the three
hermes_state_repairsites so the diff is reviewable.The same bare interpolation exists at other guidance sites (
hermes_cli/doctor_state.py,agent/turn_explainers.py,gateway/run_notifications.py,hermes_cli/sessions_cmd.py,hermes_state.py) — happy to follow up with those in a second PR routed through the samehelper, or to inline
shlex.quoteat the sites instead if you'd rather not take the helper.Your call on the shape.
Verification — a real shell and the real parser, not a shlex simulation
tests/hermes_cli/test_state_repair_hint_pasteable.pyrefuses the tautology of simulatingthe paste with
shlexwhile the implementation decides safety withshlex:_disk_budgetis patched to drive each realrefusal branch — the message code itself is untouched);
/bin/bash(printf "%s\0", NUL-delimited so a wordcontaining whitespace survives the round trip);
sessions recoversubparser built bybuild_sessions_parser, and it must bind the exact path to--source.3 sites × 3 hostile directory shapes (
My Drive,Program Files,First Last), plusover-fix controls pinning that ordinary paths gain no quotes, plus the leading-
-attachedform, plus the no-
hermes_clifallback, plus a choke-point test.Regression check
Ran the touched subsystem on both trees, back to back, same conditions:
Identical node IDs on both (
test_cross_vm_fs_wal_refusal×3,test_guest_durability_barriers, and thetest_state_db_repair_non_destructiveerror) —inherited from the base, not added here. In particular the #100368 salvage gate
(
test_sqlite3_cli_salvage_gate.py, 19 tests) andtest_corruption_recovery_guidance.pyare green: the guidance still names the safe
sessions recoverlane and still warns againstpointing a raw
sqlite3shell at the live database.ruff checkclean,git diff --checkclean.Known limit
A literal backtick inside
HERMES_HOMEcannot be made safe inside a backticked span —that is a message-format limit, not an escaping one, and this PR does not attempt it.