backlog: file #1097, the worktree gate's interpreter recursion misses PowerShell flag prefixes (live fail-open) - #282
Merged
Conversation
…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.
…doc-message # Conflicts: # docs/BACKLOG.md
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.ps1recurses one level into an interpreter argument, because that argument is quoted butis code that runs. The recursion keys on a fixed alternation:
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_RANpayload):-Command-Comm-Com-Cm-C-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')onpwsh -NoProfile <flag> "git reset --hard":-CommandTrue,-CTrue,-CommFalse,-ComFalse.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 thesettings.jsonhook wiring), 93,581 bytes,last written 2026-08-07 11:19. LF-folded, it is byte-identical to
scripts/hooks/worktree_gate.ps1inthis 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 DENYwith zero other verdict movement, so it is resumable (
resumeFromRunId: "wf_b4050e2f-77d", re-apply thebanked patch first).
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.