Skip to content

[finding] guard-main-checkout-bash.sh does not strip # comments, so a comment containing ; and > is analysed as a write and blocks the command #10720

Closed as duplicate

Description

@claude

Hit directly while running a pure-curl command from the devx PM seat. .claude/** is a
governed surface, so this is filed as a finding rather than fixed. Filed unassigned.

What happened

The command was four curl calls plus one bash comment:

# sitting 1 landed; card stays open and ready for sitting 2 -> pm:dispatched must go, pm:queue returns

The hook blocked it:

⛔ Blocked: this Bash command WRITES into the shared PRIMARY checkout, not a worktree.
   command: card stays open and ready for sitting 2 -> pm:dispatched must go, pm:queue returns
   target:  pm:dispatched
   repo:    /home/user/objectstack

There is no write in that command. The hook read the comment.

Mechanism, from the hook's own source

  1. split_segments() (:128-139) splits on ; | & ( ) { } \n, honouring quotes. My comment
    contains a ;, so it is cut in two and the trailing clause becomes its own "segment".
  2. Nothing strips # comments. The hook strips heredoc bodies (strip_heredocs, :97)
    and it makes separators inert inside quotes — but a # comment is neither, so the tail of the
    comment is analysed as a command.
  3. That tail contains ->. The tokenizer marks > as an unquoted redirection operator
    (:171, :244-245) and takes the next token, pm:dispatched, as the redirect target.
  4. target_is_shared_checkout() (:189) resolves that relative target against the cwd — the
    shared primary checkout — and the guard fires.

Why this is a defect rather than acceptable over-blocking

The hook's own header declares "PRECISION OVER RECALL" (:32) and deliberately declines
several genuinely-ambiguous shapes: node -e, awk >, targets containing $VAR / globs,
relative targets with no cwd. The intent is explicit at :125:

"A separator inside '…' or "…" does NOT split, so writing about the ban is never
caught."

That is exactly the property a comment should have, and it is implemented for quotes and for
heredocs but not for #. A comment is the purest case of writing about a write rather than
performing one.

The concrete harm is the remedy the block points at. The message's only escape is
OS_ALLOW_MAIN_EDITS=1, which disables the guard for the whole command. A false positive on
a harmless comment trains the operator to reach for the total-override flag on commands that
genuinely do not need it — and the next command it is reached for might. A guard whose false
positives are cheapest to resolve by turning it off is being pushed in the wrong direction.

Same family as #5790, which is why this hook exists at all: the Edit-only guard let the
identical write slip through as a shell command. This is that lesson's mirror image — the shell
parser now sees writes the Edit hook could not, including ones that are not there.

Direction (not a decision)

Strip # comments before split_segments(), with the same care strip_heredocs() already
takes: a # only begins a comment at the start of a word, and never inside '…', "…", or a
heredoc body — so curl 'https://…/#frag', sed 's/#//' and grep '#' must be untouched. The
existing quote-aware scanner already has the state needed to decide that.

⛔ Not proposing any change to what the hook blocks once it has a real command. The recall side
is right; this is only about what counts as a command.

Repro

Any Bash command containing a comment with both a ; and a > — e.g.
# step one; then a -> b — from a cwd inside the shared checkout.

Refs

.claude/hooks/guard-main-checkout-bash.sh :32, :97, :125, :128-139, :171, :189,
:244-245 · #5790 (why the Bash-side guard exists) · governed surface, Prime Directive #14


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    Triage (dedup): duplicate of #10570 (filed 03:44Z today, domain:skills · finding) — same defect, same file: guard-main-checkout-bash.sh does not strip # comments, so an unquoted > inside a comment is analysed as a redirect and false-blocks the command. Closing this one as the later filing; the mechanism walkthrough, the "precision over recall" argument, the total-override-flag harm analysis and the proposed fix direction in this card's body are the richer record — whoever grades/implements #10570 should read this card alongside it (cross-linked there). No information lost: closed cards remain readable.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions