Skip to content

fix(OMN-19542): the PR-body stamp guard reads heredocs and create-then-edit commands instead of refusing them - #2341

Merged
jonahgabriel merged 1 commit into
devfrom
jonah/omn-19542-stamp-guard-parse-fix
Sep 25, 2026
Merged

jonahgabriel merged 1 commit into
devfrom
jonah/omn-19542-stamp-guard-parse-fix

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

OMN-19542: the PR-body stamp guard reads heredocs and create-then-edit commands

Ticket: OMN-19542 (child of OMN-19019, the stamp-guard slice; FP-B there). Guard of record: OMN-18335. Authority: the operator's question about this guard in session omni-home-83 (~07:00Z 2026-09-25), recorded with lane occ-stamp-rebind's tally in the rolling work ledger (TERMINAL row 3319, FRICTION row 3317). The WIP-freeze RULING (OMN-17427) exempts it as a lander fix.

Why

The guard split each command with shlex, which has no notion of a here-document and treats a newline as plain whitespace. Its 22 refusal rows since 2026-09-23T18:07Z (ledger row 666 onward, 64 folded repeats) break down as follows (tally: ledger row 3319):

  • 15 untokenisable (rows 666, 880, 974, 1204, 1250, 1337, 1440, 1541, 1705, 1923, 2111, 2252, 2483, 2690, 2926): an apostrophe inside a quoted heredoc body. Almost all of these only created a PR, posted a comment or ran a Python script.
  • 3 unreadable body file (rows 2416, 3164, 3209): one command fetched the live body into a file, changed it, and then edited the PR. The guard read the file before the command had written it.
  • 4 real dropped-line refusals (rows 1028, 1070, 2793, 3101). These must keep refusing.

What changes

  • Tokenizer. shell_words.tokenize gains a keep_redirects option that emits Redirect tokens, so the guard knows which file each command writes. The default output is unchanged, so the worktree and shared-tree guards are unaffected (315 of their tests pass).
  • File model. The guard walks the command in order and keeps a small model of the files it writes. A body file written by a heredoc, printf, echo, cat, tee or cp, or filled with the live body by gh pr view --json body --jq .body or gh api .../pulls/N --jq .body, is judged on that text. The disk file as it stood at hook time is never used. --body-file - resolves from a heredoc, < or a pipe. --body "$(cat <<'EOF' ...)" resolves too.
  • Fail closed, with the workaround named. Some bodies are only known after the command runs: a file rewritten by an interpreter, a script or sed -i, an expanding heredoc with a command substitution, or a write to a path that cannot be resolved. These are still refused. The message now names the writer, and says to prepare the file in one Bash call and run the edit in the next. A command that cannot be split into words at all is refused only when its text names gh followed by pr edit or api.
  • A fail-open this found. shlex never emits a newline separator, so the old parser could not see an edit on any line after the first. For example, cd /tmp followed on the next line by gh pr edit 1 --body gone was admitted. A regression test now pins this.
  • Smaller fixes.
    • do and then are keywords, not programs.
    • Relative body-file paths resolve against the session cwd from the hook payload, following any cd in the command. Before, they resolved against HOME, where the wrapper runs.
    • gh api -f body=@x is literal, and only -F reads a file.
    • The wrapper's pre-filter now also passes --input, which before never reached the decision core.

The stamp-drop rule itself is unchanged.

  • RED first. Against the unchanged guard, the new regression file had 32 failed and 6 passed.

  • Green. 105 passed across tests/hooks/test_pr_body_stamp_guard_parse_omn_19019.py (39), tests/hooks/test_pr_body_stamp_guard.py, tests/hooks/test_pr_body_stamp_guard_rebind_omn_18853.py (the omniclaude#2340 suite, unchanged) and tests/hooks/test_shell_words_redirects.py. The worktree-add and shared-tree git guard suites also pass: 315 tests. The hooks baseline suite passes (25). mypy --strict and ruff are clean on the changed modules, and pre-commit passes on the changed files.

  • Replay of the refusing commands. 90 distinct refused commands were harvested from the refusing sessions' transcripts: 48 untokenisable, 23 unreadable and 19 dropped. They were replayed through both guards with a stub live reader.

    • Untokenisable: 44 are now admitted. Of the other 4, 2 are real edits judged on their heredoc text, and 2 are Python rewrites refused with the workaround.
    • Every dropped-line refusal still refuses.
    • No command that refused before is admitted now, except the two ledger-row-3209-shaped commands whose body is fully determined: the live body plus appended printf lines.
  • Calibration. Over 1,294 transcript Bash commands since 2026-09-22 that pass the wrapper's pre-filter, there were 0 crashes. 46 commands admitted before are refused now. In all 46, the old parser saw no edit at all: the edit sat on a line after a heredoc or a cd. 37 of them rewrite the body file with Python or sed before sending it, so the old guard, had it seen them, would have judged the pre-rewrite file.

  • Live read-only probe, 09:43Z. The worktree's hook wrapper was fed four payloads against omnibase_core#1762, with the live description read by the guard. The commands themselves were never run.

    • create-only with an apostrophe: exit 0
    • heredoc body dropping the line, then edit: exit 2
    • heredoc body keeping every line, then edit: exit 0
    • fetch, Python rewrite, then edit: exit 2, with the workaround named

    All four got the expected exit code. The probe's refusal row (ledger 3477) is annotated by a CORRECTION row.

  • Delegation. The tokenizer tests were drafted by onex delegate --task-type test, run 18a8b05c-dd56-4f0c-8330-f91dadf03aa9 (Qwen3.8-27B, about 21 s, rc 0). Result: 8 of 9 tests were correct as drafted. One assertion miscounted the words (it said 2; the right count is 3) and was corrected in review.

Lab

Not runtime-affecting. This is a session tool-seam hook that runs in no deployed runtime, the same as OMN-18335. The live probe above is the readback.

Deploy note

Hooks load from the omniclaude canonical clone's working tree, not from the plugin cache. After merge, that clone has to be fast-forwarded to origin/dev, and a new session started, before the fix is live.

Residuals, stated

  • A Python or shell interpreter anywhere earlier in the same command makes every body file unknown, even when it cannot touch the body file (for example uv run pytest ...; gh pr edit ... --body-file f). The refusal names the workaround. This is deliberate: an interpreter can write any file.
  • bash -c '<edit>' and eval are still not parsed as nested commands. That is unchanged from before.

Evidence-Ticket: OMN-19542
Evidence-Source: OCC#11247

…n-edit commands instead of refusing them

The guard split commands with shlex, which has no notion of a here-document
and treats a newline as whitespace. Of its 22 logged refusal rows, 15 were
commands it could not tokenise (an apostrophe in a quoted heredoc body) and 3
read a body file the same command had not written yet; only 4 were real
dropped-line refusals (ledger:3319).

- Split with the shared shell tokenizer; shell_words.tokenize gains
  keep_redirects so the guard can see which file each command writes.
- Walk the command in order with a small file model: a body file written by a
  heredoc, printf, echo, cat, tee, cp, or the live body fetched with a read is
  judged on that text, never on the stale disk file. --body-file - and
  --body "$(cat <<'EOF' ...)" resolve from the heredoc.
- A body only known after the command runs (interpreter or sed -i rewrite,
  expanding heredoc with a substitution, unresolvable write target) still
  refuses, now naming the writer and the workaround: prepare the file in one
  Bash call, edit in the next.
- Fixes a fail-open: an edit on any line after the first was never seen,
  because shlex never emitted the newline separator. Also: `do`/`then` are
  keywords, not programs; relative paths resolve against the session cwd;
  gh api -f body=@x is literal; the wrapper pre-filter now passes --input.

The stamp-drop rule is unchanged. Regression set: 39 tests in
tests/hooks/test_pr_body_stamp_guard_parse_omn_19019.py; tokenizer tests in
tests/hooks/test_shell_words_redirects.py were drafted by onex delegate run
18a8b05c-dd56-4f0c-8330-f91dadf03aa9 (one wrong assertion corrected).

Onex-Lane: stamp-guard-parse-fix
Onex-Session: d4d20bbca21e42cfb74215bec0aa6367
@jonahgabriel jonahgabriel added the hold:auto-merge Refuses auto-merge arming (OMN-18179 hold gate) label Sep 25, 2026
@onexbot-occ-writer

Copy link
Copy Markdown
Contributor

No OCC evidence companion was minted for this PR.

product PR is a draft; companion suppressed (F-17)

Matched in the state: draft

To clear this: Mark the PR ready for review. The ready_for_review trigger re-fires the born path and the companion mints then.

Reported by node_occ_companion_effect (decline code pr_draft, OMN-16665). This decision was made against the PR's LIVE state at compute time, not at publish time — a green publisher job only means the command reached the broker.

@jonahgabriel
jonahgabriel marked this pull request as ready for review September 25, 2026 09:53
@onexbot-occ-writer

Copy link
Copy Markdown
Contributor

No OCC evidence companion was minted for this PR.

the PR body contains 'WIP' at line 3, which the F-17 hold-marker rule reads as a do-not-merge/WIP hold; companion suppressed

Matched in the body line 3: WIP

To clear this: The hold is honoured exactly as written. Reword or remove 'WIP' in the PR body — note that a CONDITIONAL hold ('do not merge until CI is green') is indistinguishable from an unconditional one to this rule — then re-run the OCC Companion Effect Publisher workflow_dispatch for this PR.

Reported by node_occ_companion_effect (decline code hold_marker_text, OMN-16665). This decision was made against the PR's LIVE state at compute time, not at publish time — a green publisher job only means the command reached the broker.

jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 25, 2026
…iclaude#2341 (#11247)

* evidence(OMN-19542): author OCC companion for OmniNode-ai/omniclaude#2341

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 0252fd1cd68485341fd6fb5467ae4311b0113694.

* evidence(OMN-19542): self-bind OCC#11247 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 0
Models succeeded: deepseek-r1,qwen3-review-b


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Reviewer ran but every model failed mid-run No (pilot)
infra_error cli_review failed to run at all (install/crash) after 2 attempts Yes
unavailable_fork_isolation Fork PR — the LAN-only model is unreachable and the fleet is off limits Yes

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

@jonahgabriel
jonahgabriel merged commit 66d7d05 into dev Sep 25, 2026
201 of 203 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-19542-stamp-guard-parse-fix branch September 25, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold:auto-merge Refuses auto-merge arming (OMN-18179 hold gate)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant