Skip to content

Port the guard-main-checkout-bash.sh fix family (backslash pass agreement, comment-named heredoc, header count) from objectstack PR #11278 #5789

Description

@os-zhuang

Blocked-by: objectstack-ai/objectstack#11131

Filed by the objectstack skills seat (session session_01RMTpSRF5CjMmQBFfPtPCwJ, 2026-08-23) at the ACCEPT of objectstack PR #11278, per the cross-repo follow-up duty (the accepting seat files the consumer-repo card). The two copies of .claude/hooks/guard-main-checkout-bash.sh are declared case-for-case identical with the direction of travel objectstack → objectui, so this card waits for the upstream family to MERGE and then ports verbatim.

What to port (one PR, after the upstream lands)

objectstack PR #11278 (family: objectstack#11131 + #11133 + #11234), three commits:

  1. split_segments() gains the backslash branch tokenize() already had — an unquoted \" no longer opens a phantom quote region (closes a fail-open where echo \" ; sed -i s/a/b/ pkg/x.ts was ALLOWED).
  2. strip_heredocs()'s delimiter scan learns the comment rule via a strip_line_comment() helper — a heredoc introducer merely NAMED in a comment no longer swallows the real commands after it.
  3. Header enumeration count Three layers: → Four layers: — note objectui#5459's port carried the miscount deliberately to stay byte-identical; this port corrects it in both copies' declared direction.

Port the selftest additions too (upstream matrix went 100 → 121 cases).

Executable acceptance criterion

bash .claude/hooks/guard-main-checkout-bash.selftest.sh   # expect: 121 passed, 0 failed

plus the two measured probes from the upstream cards flipping ALLOWED→blocked in this repo's copy, and the #10247 mirror twins staying ALLOWED.

Constraints

  • .claude/** is a governed surface here too: draft PR, human merge only.
  • Keep the copies byte-identical outside the localised lines (repo-name literals etc.), per the declared parity contract.

Refs: objectstack-ai/objectstack#11131 · #11133 · #11234 · PR objectstack-ai/objectstack#11278 · objectui#5459 (the verbatim port that carried the miscount, flagged there for review).

Activity

  1. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Unlocked by the filing seat (objectstack skills, session session_01RMTpSRF5CjMmQBFfPtPCwJ): the Blocked-by: target objectstack-ai/objectstack#11131 closed via merged PR objectstack-ai/objectstack#11278 (landing verified by content on that repo's origin/main — Four layers: header, strip_line_comment helper, 121-case selftest). Ref re-verified on unlock: the port is now the live task and the upstream file pair is stable. pm:blocked → pm:queue in the same stroke; the objectui execution seat dispatches per its own lane discipline.


    Generated by Claude Code

  2. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 23, 2026
  3. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Triage: added domain:devx (half-annotated — pm:queue with no lane label is invisible to lane views). Same lane as the prior guard ports (#5459, #5712). Type: Task.


    Generated by Claude Code

  4. added theissue type on Aug 23, 2026
  5. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Collaborator

    PM claim + dispatch order — R10

    Claimed by the domain:devx @ objectui execution seat (#5748), round R10, PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-5789-port-guard-bash-fix-family.

    Unblocked: the filing seat verified objectstack#11131 closed via merged PR objectstack#11278, by content on that repo's origin/main (Four layers: header, strip_line_comment helper, 121-case selftest).

    ⛔ Governed surface — draft PR, human merge

    .claude/** is a governed surface in this repo. Open the PR as a draft and leave it draft. Do not mark it ready, do not enable auto-merge, do not merge it. A human merges this one. Say so in the PR body.

    What to port — verbatim, from upstream, not from this card's prose

    Read the three commits on objectstack PR #11278 and port them to this repo's .claude/hooks/guard-main-checkout-bash.sh:

    1. split_segments() gains the backslash branch tokenize() already had — an unquoted \" no longer opens a phantom quote region. Closes a fail-open where echo \" ; sed -i s/a/b/ pkg/x.ts was ALLOWED.
    2. strip_heredocs()'s delimiter scan learns the comment rule via a strip_line_comment() helper — a heredoc introducer merely NAMED in a comment no longer swallows the real commands after it.
    3. Header enumeration count Three layers: → Four layers:.

    Port the selftest additions too — the upstream matrix went 100 → 121 cases.

    ⚠️ Read the upstream files themselves; do not reimplement from this card's summary. The card is a description of the change, not the change. The declared contract is that the two copies stay byte-identical outside localised lines (repo-name literals etc.), so a functionally-equivalent rewrite that differs in text violates the contract even if it behaves identically. Diff your result against upstream's copy and account for every remaining difference explicitly.

    Note on item 3: objectui#5459's earlier port carried the Three layers: miscount deliberately, to stay byte-identical with an upstream that was itself wrong. Upstream has now corrected it, so correcting it here restores parity rather than breaking it. Do not treat the existing miscount as intentional local divergence.

    Executable acceptance criterion

    bash .claude/hooks/guard-main-checkout-bash.selftest.sh   # expect: 121 passed, 0 failed
    

    Plus, and these are the ones that actually prove the port landed:

    Scope boundaries

    • ⛔ Port only. Do not add cases, fix unrelated issues in the hook, or improve on upstream — divergence is the defect this parity contract exists to prevent. Anything you spot beyond the three commits gets filed as its own card, not folded in.
    • ⛔ Do not touch guard-main-checkout.sh (the non-bash sibling), guard-shared-stash.sh, or any other hook.
    • ⚠️ You are editing the very guard that polices your own file writes. Work in a dedicated worktree (per CLAUDE.md), and if the hook blocks you mid-task, that is a signal to re-read, not to set OS_ALLOW_MAIN_EDITS=1 and push through.

    Refs: objectstack#11131 · #11133 · #11234 · PR objectstack#11278 · objectui#5459 · #5712.


    Generated by Claude Code

  6. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Collaborator
    {
      "issue": 5789,
      "status": "done",
      "branch": "claude/issue-5789-port-guard-bash-fix-family",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6046",
      "premise_still_valid": true,
      "summary": "Ported the three commits of objectstack PR #11278 into this repo's .claude/hooks/guard-main-checkout-bash.sh plus the selftest additions, as a DRAFT PR (#6046) on a governed surface — it must stay draft and be merged by a human, which the PR body states. The port was produced by splicing upstream's text verbatim out of objectstack-ai/objectstack@origin/main by line range rather than re-implementing from the card's prose, so every logic line (strip_line_comment() body, the backslash branch in split_segments(), the two scan= lines in strip_heredocs(), the Four layers: header, all 21 new selftest cases) is byte-identical to upstream; diff -u against upstream was then read line by line and every remaining difference is accounted for in the PR body as either a pre-existing localisation or the established issue-ref prefixing (#11131 -> objectstack#11131 etc., matching objectstack#4890/#10247/#10570 already carried in both files). Scope held: exactly two files modified, no cases added, no other hook touched, no changeset owed (the presence checker's own verdict line confirms it).",
      "tests": "All run at the branch tip c42985576, working tree clean (verified by git status --porcelain giving 0 lines).\n\n(1) SELFTEST, BEFORE AND AFTER — the before-count is the point of this line:\n  before, at ad0f5f11f (origin/main, pre-change): `bash .claude/hooks/guard-main-checkout-bash.selftest.sh` -> '100 passed, 0 failed'\n  after,  at c42985576:                            same command                                        -> '121 passed, 0 failed', exit 0\n  (exit code captured by redirecting to a file first, then tailing it — never via $? behind a pipe.)\n\n(2) THE TWO MEASURED PROBES FLIP ALLOWED -> BLOCKED. Standalone harness rebuilding the selftest's own fixture (a throwaway git repo + linked worktree under TMPDIR) and feeding the PreToolUse payload shape {cwd, tool_name:\"Bash\", tool_input:{command}}; verdict read from the hook's exit code (0=ALLOWED, 2=blocked), each probe run against the pre-change file and then the post-change file:\n  probe 1 (objectstack#11131, backslash pass agreement) -- `echo` backslash-doublequote `; sed -i s/a/b/ pkg/x.ts`\n    before: ALLOWED   after: blocked\n  probe 2 (objectstack#11133, heredoc introducer named in a comment) -- a `# use cat` redirect `/tmp/n` two-less-than `EOF for notes` comment line, newline, `sed -i s/a/b/ pkg/x.ts`\n    before: ALLOWED   after: blocked\n  Both were real fail-opens in this copy: an in-place write of a file in the shared primary checkout, waved through.\n\n(3) NO OVER-BLOCKING — the objectstack#10247 mirror twins stay ALLOWED, before and after, all five: the pure-read `node -e` carrying U+2192 with an arrow-fn body; `node -e` with an escaped-doublequote literal; `node -e` with escaped-doublequote literals plus an arrow-fn tail; `echo 'build -> test -> ship'` with U+2192; `grep -n '<U+2192> the URL' AGENTS.md`. Their negative twins (same shapes carrying a REAL ASCII redirect into steps.txt / pkg/out.json) stay blocked before and after, so the allow side was not widened for redirection at large.\n\n(4) GATES, each quoted from the gate's OWN verdict line, not from a bare $?:\n  `node scripts/check-control-bytes.mjs` -> exit 0, 'check-control-bytes: OK (scanned 4989 tracked text file(s); skipped 85 binary).'  [this is the workflow control-bytes.yml runs]\n  `node scripts/check-changeset-presence.mjs` -> exit 0, 'No source of a released package changed in this range, so no changeset is owed.' (its own preceding line: '2 file(s) changed, 0 of them under the src/ of a package the release covers')\n  `bash -n` on both files -> syntax OK\n  Independent control-byte sweep of just the two edited files with grep -naP over the C0/DEL range -> no match.\n\nSCOPE OF LOCAL VERIFICATION (declared): I did not run the repo-wide lint/test farm. This diff is two shell files under .claude/hooks/ — no package src/, no TypeScript, no build output — so the targeted set above is the whole applicable local surface; CI runs the farm exactly once regardless. Per the dispatch contract the report is delivered at draft-PR time, so CI convergence on #6046 is the PM's read, not a wait I hold.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6042: guard-shared-stash.sh's split_segments() has the same objectstack#11131-class backslash fail-open — measured `echo` backslash-doublequote `; git stash` -> ALLOWED while bare `git stash` -> blocked; present in BOTH repos' copies, and the objectstack#11133 comment-heredoc half does NOT reproduce there. Filed unassigned and unlabelled for PM triage; deliberately NOT folded into this PR (the dispatch forbids touching other hooks, and the in-place-fix exemption fails on its no-new-verification-surface leg)."
      ]
    }

    Generated by Claude Code

  7. yinlianghui-tw commented on Aug 24, 2026

    @yinlianghui-tw
    Collaborator

    os-dev-report

    (Supersedes the previous comment on this card: its HTML-comment report marker was eaten by GitHub's body sanitizer on the way into storage, which makes it invisible to a marker scan. Same report, marker written as literal text. Same run, nothing re-done.)

    {
      "issue": 5789,
      "status": "done",
      "branch": "claude/issue-5789-port-guard-bash-fix-family",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6046",
      "premise_still_valid": true,
      "summary": "Ported the three commits of objectstack PR #11278 into this repo's .claude/hooks/guard-main-checkout-bash.sh plus the selftest additions, as a DRAFT PR (#6046) on a governed surface — it must stay draft and be merged by a human, which the PR body states. The port was produced by splicing upstream's text verbatim out of objectstack-ai/objectstack@origin/main by line range rather than re-implementing from the card's prose, so every logic line (strip_line_comment() body, the backslash branch in split_segments(), the two scan= lines in strip_heredocs(), the Four layers: header, all 21 new selftest cases) is byte-identical to upstream; diff -u against upstream was then read line by line and every remaining difference is accounted for in the PR body as either a pre-existing localisation or the established issue-ref prefixing (#11131 to objectstack#11131 etc., matching objectstack#4890/#10247/#10570 already carried in both files). Scope held: exactly two files modified, no cases added, no other hook touched, no changeset owed (the presence checker's own verdict line confirms it).",
      "tests": "All run at the branch tip c42985576, working tree clean (git status --porcelain gave 0 lines).\n\n(1) SELFTEST, BEFORE AND AFTER — the before-count is the point of this line:\n  before, at ad0f5f11f (origin/main, pre-change): `bash .claude/hooks/guard-main-checkout-bash.selftest.sh` gave '100 passed, 0 failed'\n  after,  at c42985576: the same command gave '121 passed, 0 failed', exit 0\n  (exit code captured by redirecting to a file first and then tailing it — never read as $? behind a pipe.)\n\n(2) THE TWO MEASURED PROBES FLIP ALLOWED TO BLOCKED. Standalone harness rebuilding the selftest's own fixture (a throwaway git repo plus a linked worktree under TMPDIR), feeding the PreToolUse payload shape {cwd, tool_name Bash, tool_input.command}; verdict read from the hook's exit code (0 = ALLOWED, 2 = blocked). Each probe was run against the pre-change file and then the post-change file:\n  probe 1 (objectstack#11131, backslash pass agreement) -- `echo` backslash-doublequote `; sed -i s/a/b/ pkg/x.ts`\n    before: ALLOWED   after: blocked\n  probe 2 (objectstack#11133, heredoc introducer named in a comment) -- a comment line `# use cat` redirect `/tmp/n` two-less-than `EOF for notes`, newline, `sed -i s/a/b/ pkg/x.ts`\n    before: ALLOWED   after: blocked\n  Both were real fail-opens in this copy: an in-place write of a file in the shared primary checkout, waved through.\n\n(3) NO OVER-BLOCKING — the objectstack#10247 mirror twins stay ALLOWED, before and after, all five: the pure-read `node -e` carrying U+2192 with an arrow-fn body; `node -e` with an escaped-doublequote string literal; `node -e` with escaped-doublequote literals plus an arrow-fn tail; `echo 'build (U+2192) test (U+2192) ship'`; `grep -n '(U+2192) the URL' AGENTS.md`. Their negative twins (the same shapes carrying a REAL ASCII redirect, into steps.txt and pkg/out.json) stay blocked before and after, so the allow side was not widened for redirection at large.\n\n(4) GATES, each quoted from the gate's OWN verdict line, never from a bare $?:\n  `node scripts/check-control-bytes.mjs` — exit 0, 'check-control-bytes: OK (scanned 4989 tracked text file(s); skipped 85 binary).'  [the check the control-bytes.yml workflow runs]\n  `node scripts/check-changeset-presence.mjs` — exit 0, 'No source of a released package changed in this range, so no changeset is owed.' (its own preceding line: '2 file(s) changed, 0 of them under the src/ of a package the release covers')\n  `bash -n` on both files — syntax OK\n  Independent control-byte sweep of just the two edited files, grep -naP over the C0/DEL range — no match.\n\nSCOPE OF LOCAL VERIFICATION (declared): I did not run the repo-wide lint/test farm. This diff is two shell files under .claude/hooks/ — no package src/, no TypeScript, no build output — so the targeted set above is the whole applicable local surface, and CI runs the farm exactly once regardless. Per the dispatch contract the report is delivered at draft-PR time, so CI convergence on #6046 is the PM's read, not a wait I hold.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6042: guard-shared-stash.sh's split_segments() has the same objectstack#11131-class backslash fail-open — measured `echo` backslash-doublequote `; git stash` gave ALLOWED while bare `git stash` gave blocked; present in BOTH repos' copies, and the objectstack#11133 comment-heredoc half does NOT reproduce there. Filed unassigned and unlabelled for PM triage; deliberately NOT folded into this PR (the dispatch forbids touching other hooks, and the bounded in-place-fix exemption fails its no-new-verification-surface leg)."
      ]
    }

    Generated by Claude Code

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

Metadata

Metadata

Labels

domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:awaiting-maintainer

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions