Skip to content

backlog: file #1097, the worktree gate's interpreter recursion misses PowerShell flag prefixes (live fail-open) - #282

Merged
wshallwshall merged 3 commits into
mainfrom
claude/gate-1086-heredoc-message
Aug 7, 2026
Merged

backlog: file #1097, the worktree gate's interpreter recursion misses PowerShell flag prefixes (live fail-open)#282
wshallwshall merged 3 commits into
mainfrom
claude/gate-1086-heredoc-message

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Authored by the session in worktree nice-payne-4dcee0; pushed and opened by the coordinator. Docs only
-- one BACKLOG item, +28 lines. The fix is deliberately not in this PR.

The defect, and it is LIVE on the installed gate

worktree_gate.ps1 recurses one level into an interpreter argument, because that argument is quoted but
is code that runs. The recursion keys on a fixed alternation:

'(?:^|\s)(?:-c|-lc|-ec|-Command|-EncodedCommand)\s+"([^"]*)"',
"(?:^|\s)(?:-c|-lc|-ec|-Command|-EncodedCommand)\s+'([^']*)'",

PowerShell accepts any unambiguous prefix of a parameter name. So spellings exist that execute and are
not on that list -- and their argument is then blanked as an ordinary quoted span, which makes its contents
invisible to every rule, not only rule 3c.

Coordinator verification -- reproduced independently, not taken on report

1. Which prefixes actually execute (harmless Write-Output PREFIX_RAN payload):

flag executes recursed into by the gate regex
-Command yes yes
-Comm yes NO
-Com yes NO
-Cm no (ambiguous) n/a
-C yes yes (matches -c)

So the bypassing family is bounded -- -Com, -Comm, and the intermediate spellings up to -Command --
which is what makes this fixable rather than open-ended.

2. Regex confirmation, [regex]::IsMatch(..., 'IgnoreCase') on pwsh -NoProfile <flag> "git reset --hard":
-Command True, -C True, -Comm False, -Com False.

3. It is LIVE, which required checking the INSTALLED copy rather than the source. The gate executes
from C:\Users\Scott\.claude\hooks\worktree_gate.ps1 (per the settings.json hook wiring), 93,581 bytes,
last written 2026-08-07 11:19. LF-folded, it is byte-identical to scripts/hooks/worktree_gate.ps1 in
this checkout and carries the same fixed alternation. This repo has measured genuinely stale installed
hooks before with zero instruments watching them, so "the source is wrong" and "the live gate is wrong"
are separate claims; both hold here.

The obvious fix is insufficient, and that is the load-bearing part

Adding the three spellings to the list closes every form on the committed gate, but on a
message-blanking gate it closes only the single-line form and leaves the here-string route open behind it.
The here-string form denies today only by accident -- every line of a multi-line span reaches the
scanner raw -- and that accident is removed by any correct message-blanking change.

So the fix must be verified against the gate as it will SHIP, not as it is today.

Why the #1086 fix was reverted, and the lesson worth carrying

The #1086 work is banked, not abandoned. It was rejected because two of three verifiers independently found
a new fail-open in it; the design was right (key on the consuming flag, blank after interpreter
recursion, both pinned by killed mutants) and only the classifier was wrong -- one open-ended cluster regex
matched -Com. Two verifiers measured the closed-flag-set replacement as returning the bad rows to DENY
with zero other verdict movement, so it is resumable (resumeFromRunId: "wf_b4050e2f-77d", re-apply the
banked patch first).

The third verifier ACCEPTED it -- because its corpus contained no -Com payload. Its evidence could
not see the class it was accepting. That is #1000's shape occurring inside the verification of a #1000
fix. Do not read "one verifier accepted" as evidence; read what its corpus covered.

Claim 1086 remains held by nice-payne-4dcee0.

Auto-merge not armed -- queued behind merge train #275. Docs-only, so it costs no CI slot when its turn
comes.

…om every rule

Found by the adversarial verification of a candidate fix for #1086, and separated from
that candidate by measuring both against the committed gate: this half is PRE-EXISTING
and LIVE.

worktree_gate.ps1:285 recurses into an interpreter argument for a FIXED list of
spellings. PowerShell accepts any unambiguous PREFIX, so these run code and are not on
the list:

    pwsh -NoProfile -Com  "..."   -> runs
    pwsh -NoProfile -Comm "..."   -> runs
    pwsh -NoProfile -Cm   "..."   -> not recognized, so the family is bounded

Their argument is therefore blanked as an ordinary quoted span and its contents are
invisible to EVERY rule, not only 3c. Whatever a session may not do directly, it may do
inside one of these.

THE SINGLE-LINE FORM ALREADY ALLOWS TODAY. The here-string and multi-line forms still
deny, but only BY ACCIDENT -- every line of a multi-line span reaches the scanner raw, so
the disarm is caught as ordinary text rather than because the interpreter was recognised.
That accident is load-bearing and trivially removed: the #1086 candidate blanked message
bodies correctly and in doing so converted the accident into a clean fail-open across all
forms. Which is why the item says the fix must be verified against the gate as it will
ship, not as it is.

AND THE OBVIOUS FIX IS INSUFFICIENT, MEASURED: adding the three spellings to the list
closes every form on the committed gate, but on a message-blanking gate it closes only
the single-line one and leaves the here-string route open behind it.

The item asks for prefix recognition bounded to the shells that actually accept prefixes
-- pwsh and powershell do, bash and sh do not -- and for the test to enumerate the FAMILY
from -C through -Command plus a bounding negative, because a test pinning one spelling
cannot see the next one. That is the same defect as the third verifier accepting the
candidate: its corpus contained no such payload, so its evidence could not see the class.
…es it not re-derives it

A candidate was built and put through three adversarial verifiers today. NOT READY, for
exactly one defect. Recording the correction in the item rather than leaving it in a
workflow synthesis, because the next session should spend its budget verifying a one-line
change, not rediscovering which line.

WHAT WAS RIGHT, both pinned by killed mutants and not to be undone: key on the CONSUMING
FLAG rather than the delimiter, and run the message-blanking AFTER the interpreter
recursion. The blank-before-recursion mutant is killed by seven to nine interpreter
payloads in all three mutation runs.

WHAT WAS WRONG, and it is the whole rejection: the flag classifier matched an OPEN-ENDED
letter cluster. PowerShell accepts unambiguous parameter PREFIXES, so -Com and -Comm are
working spellings of -Command that contain no lowercase c or e and end in m. They were
classified as MESSAGE and their code was blanked -- four rows went from DENY to ALLOW
across rules 3, 3c and 3d.

THE REPLACEMENT IS MEASURED, not proposed: a closed set of git message flags. Two
verifiers independently confirmed it returns those four rows to DENY, keeps all eleven
ALLOW cases and every narrowness case, and produces ZERO other verdict movement.

TWO THINGS THE CANDIDATE DID NOT DISCLOSE are now in the item. A mutant relaxing the
separator survives the entire suite -- found independently by two verifiers, same hash --
so that quantifier is load-bearing and no test says so. And two glued spellings of this
item's OWN defect remain refused, which belongs in the disclosure list.

AND THE READING RULE THE ROUND EARNED: two of three verifiers found the fail-open; the
THIRD ACCEPTED, because its corpus contained no such payload. Its evidence could not see
the class it was approving -- #1000's shape occurring inside the verification of a #1000
fix. Do not read "one verifier accepted" as evidence; read what its corpus covered.

Also records that this item and #1097 are separable and must both be fixed: #1097 is the
pre-existing interpreter-recursion gap, and this candidate would have converted #1097's
accidental here-string denial into a clean fail-open. Verify either against the gate as it
will SHIP, not as it is.
@wshallwshall
wshallwshall merged commit 3c462a9 into main Aug 7, 2026
32 checks passed
@wshallwshall
wshallwshall deleted the claude/gate-1086-heredoc-message branch August 7, 2026 20:48
wshallwshall added a commit that referenced this pull request Aug 7, 2026
Plain APPEND conflict in docs/BACKLOG.md: my side adds ## 1096., main's side
adds ## 1097. (landed via PR #282). Neither touches the other's text and the
overlapping-heading-number count between the sides is ZERO, measured. Kept
both, ascending: 1096 then 1097.

THIS CONFLICT WAS ENGINEERED TO LAND HERE RATHER THAN ON #282. Both PRs append
to the same file tail, so whichever merged second was always going to conflict.
#282 was armed FIRST and deliberately: the ledger gate keys on which worktree
ALLOCATED a number, #1097 belongs to nice-payne-4dcee0, and that session has
now stopped -- so a conflict landing on #282 would have been unresolvable by
anyone still running. #1096 is allocated to THIS worktree, so the conflict is
resolvable here. Ordering was the whole point; it was not luck.

Verified after resolution: ## 1094. x1, ## 1095. x1, ## 1096. x1, ## 1097. x1,
conflict markers 0, CRLF preserved. My #1084 reconciliation section survived
intact, as did #1096's table row and the census line reading 109. Live table
still 109 rows with contiguous ranks. backlog_status_check.py --min-items 150
OK at 368 items, each declaring exactly one status.

LEDGER DRIFT NOTE, stated rather than quietly absorbed: #1097 arrived with no
table row, so the gap between parse_items' open count and the census table
grows by one. That is the pre-existing drift already recorded in #1096's own
commit (52 open items with no row, 11 rows naming a closed item, the two sets
cancelling on a total). This merge does not fix it and does not make it worse
by any mechanism of mine -- it simply carries it forward, which is what a
correct filing does. The reconcile pass remains outstanding and needs 52 items
scored.
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