fix(guard): quote-aware shell segmentation so only real daemon kills classify (Closes #787) #789

Merged
sysadmin merged 2 commits from fix/issue-787-kill-segment-separators into master 2026-07-21 21:40:03 -05:00
2 Commits
Author SHA1 Message Date
jcwalker3andClaude Opus 4.8 aa4fe1cc7b fix(guard): make shell segment splitting quote-aware (Closes #787)
Review #497 on PR #789 found that adding `&` to the separator alternation
created a new false-positive class: the splitter was quote-unaware, so any
quoted text carrying an ampersand and a kill verb classified as a manual
daemon kill. Three commands regressed against base 35e94e10, all of which
merely mention the canonical kill string:

    git commit -m "block sleep 1 & pkill -f mcp_server.py as recovery"
    echo "docs: sleep 1 & pkill -f mcp_server.py is now detected"
    grep -rn "sleep 1 & pkill -f mcp_server.py" docs/

A false contamination marker fails review, merge, close and completion
mutations closed and only a reconciler may clear it, so this is an
operator-visible stall rather than a warning.

F1: replace the `_SEGMENT_SPLIT_RE` alternation with a quote-aware scan.
`_iter_active` yields only the character positions where a metacharacter is
syntactically active — outside single and double quotes, not backslash
escaped — and `_split_segments` separates only there. This fixes the class
rather than the three named instances, and also retires the `;` and `|`
false positives that predate #787. `&&` and `||` are still consumed whole,
so logical-separator precedence is unchanged.

F3: `_strip_subshell` now removes a trailing `)` only when it closes a
leading `(` this call stripped, so `kill $(pgrep -f myapp)` is no longer
mangled into `kill $(pgrep -f myapp`; and `_is_redirection` keeps `2>&1`,
`>&2` and `&>log` from being read as background separators. Neither changed
a verdict at the rejected head, but both are corrected here because the
splitter was reworked for F1.

Nine focused cases added: the three F1 commands, double-quoted, single-quoted
and backslash-escaped ampersands, the POSIX rule that a backslash does not
escape inside single quotes, the `;`/`|` class, the two F3 forms, and a
record-tool case asserting no marker is written for a quoted mention.

Focused suite 64 passed (47 at base 35e94e10, 55 at rejected head 6b58f04).
Full suite 11 failed, 4190 passed, 6 skipped; the same 11 failures occur
test-for-test at base and are unrelated to this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-21 20:45:17 -05:00
jcwalker3andClaude Opus 4.8 6b58f04d39 fix(guard): split on & and unwrap subshells so real daemon kills classify (Closes #787)
`runtime_recovery_guard._SEGMENT_SPLIT_RE` claimed to split a compound command
line "on shell separators", but omitted the background-job separator `&`, and no
caller stripped a subshell wrapper before tokenising. `_analyse_kill_segment`
only inspects the first command token of a segment, so in both forms the kill
verb never reached the classifier.

Measured against the previous implementation at 35e94e10:

    sleep 1 & pkill -f mcp_server.py   ->  process_kill False, contamination False
    (pkill -f mcp_server.py)           ->  process_kill False, contamination False

Changes:

- Add `&` to `_SEGMENT_SPLIT_RE` as `(?:\|\||&&|[|;&\n])`; the two-character
  logical forms stay first so `&&` and `||` are consumed whole.
- Add `_strip_subshell()` and apply it in `_split_segments()`, removing leading
  `(` and trailing `)` (including nested wrappers) before tokenising.

Both forms now classify as contamination with reason class `manual_daemon_kill`,
and the record tool writes a durable marker for each. Direct-command behaviour is
unchanged. The contamination model, gated-task set, marker lifecycle, and
reconciler-only clearing path are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-21 19:56:00 -05:00