Skip to content

[finding] guard-main-checkout-bash.sh: a heredoc introducer NAMED inside a comment makes strip_heredocs() swallow the real commands after it #11133

Closed

Description

@os-zhuang

Measured while implementing #10570 (PR #11129). Out of that card's adjudicated scope: #10570 teaches the comment rule to the two quote-aware passes (tokenize(), split_segments()), while this defect lives in the third pass, strip_heredocs(), which runs before them. Filed unassigned and NOT fixed in that PR. Dedup performed first: no existing card covers it.

What was measured

Confirmed against origin/main at 95437e7d2 (so it pre-dates PR #11129) and re-confirmed as unchanged by that PR, from a cwd inside the shared primary checkout:

# use cat > /tmp/n <<EOF for notes
sed -i s/a/b/ pkg/x.ts
                                     -> hook-exit=0   (ALLOWED)

# plain prose comment
sed -i s/a/b/ pkg/x.ts
                                     -> hook-exit=2   (blocked)

The two commands differ only in the wording of the comment. The first mentions a heredoc; the real in-place write on the next line is then never analysed.

Mechanism

strip_heredocs() scans line by line for <<WORD and drops every following line until the delimiter reappears. It runs on the raw command text and models neither quotes-per-line nor comments, so a <<EOF occurring inside prose registers as a real introducer. EOF never appears as a line of its own, so the pending delimiter is never satisfied and every remaining line — including real commands — is dropped before either quote-aware pass sees it.

This is the fail-OPEN direction, the mirror of #10570's false block, and it comes from the same root cause: a comment is text, and a pass that does not model comments will read one as structure.

Why it is not folded into #10570

#10570's grading names the two quote-aware passes and rules that what the hook blocks once it has a real command must not change. Teaching strip_heredocs() the comment rule is a change to a third pass with its own ordering constraint, and the ordering is the interesting part: comments cannot simply be stripped BEFORE heredocs, because an unbalanced apostrophe inside a heredoc BODY would then desynchronise a quote-aware comment scanner across the following lines. That is a design question rather than a mechanical edit, which is what makes it a separate card rather than a rider.

Direction (not a decision)

Two candidates worth weighing rather than one to adopt:

  1. Teach strip_heredocs() enough word/quote state to ignore a <<WORD that sits after an unquoted word-start # on the same line — narrow, and it keeps the existing pass order.
  2. Make heredoc stripping and comment stripping one scanner with shared state, so neither pass can disagree with the other about what is text. Larger, and it is the shape the hook's header already argues for when it lists its layers.

Either way the fix belongs with self-test cases in both directions, as the matrix already does for quotes, heredocs and (after PR #11129) comments.

Refs

.claude/hooks/guard-main-checkout-bash.sh — strip_heredocs() · #10570 / PR #11129 (found here) · #10720 (closed duplicate of #10570, carries the mechanism walkthrough) · #5790 (why the Bash-side guard exists) · governed surface


Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 22, 2026
  2. self-assigned this
    on Aug 23, 2026
  3. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Claim: family member — full claim, serial-constraint check and fold answer live on the anchor #11131 (session session_01RMTpSRF5CjMmQBFfPtPCwJ, branch claude/issue-11131-guard-bash-pass-agreement). This member's independent criterion: the comment-named heredoc introducer no longer swallows the real commands after it; both directions pinned in the selftest; own commit; Fixes #11133 line in the PR.


    Generated by Claude Code

  4. claude commented on Aug 23, 2026

    @claude
    Contributor

    Implemented as a family member in draft PR #11278 (branch claude/issue-11131-guard-bash-pass-agreement), which carries a Fixes #11133 line. This member’s criterion: the comment-named heredoc probe flips ALLOWED->blocked (hook-exit 0 -> 2) with real heredoc behaviour untouched; both directions pinned in the selftest; own commit 389fddc. Full os-dev report lives on the anchor #11131. Governed surface: draft, human merge only.


    Generated by Claude Code


    Generated by Claude Code

  5. removed their assignment
    on Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:skillsDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions