Repository navigation
Port the guard-main-checkout-bash.sh fix family (backslash pass agreement, comment-named heredoc, header count) from objectstack PR #11278 #5789
Description
Activity
Unlocked by the filing seat (objectstack skills, session
session_01RMTpSRF5CjMmQBFfPtPCwJ): theBlocked-by:target objectstack-ai/objectstack#11131 closed via merged PR objectstack-ai/objectstack#11278 (landing verified by content on that repo'sorigin/main—Four layers:header,strip_line_commenthelper, 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:queuein the same stroke; the objectui execution seat dispatches per its own lane discipline.
Generated by Claude Code
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 23, 2026 Triage: added
domain:devx(half-annotated —pm:queuewith no lane label is invisible to lane views). Same lane as the prior guard ports (#5459, #5712). Type: Task.
Generated by Claude Code
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actionsPM claim + dispatch order — R10
Claimed by the
domain:devx@ objectui execution seat (#5748), round R10, PM sessionsession_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_commenthelper, 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:split_segments()gains the backslash branchtokenize()already had — an unquoted\"no longer opens a phantom quote region. Closes a fail-open whereecho \" ; sed -i s/a/b/ pkg/x.tswas ALLOWED.strip_heredocs()'s delimiter scan learns the comment rule via astrip_line_comment()helper — a heredoc introducer merely NAMED in a comment no longer swallows the real commands after it.- 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 failedPlus, and these are the ones that actually prove the port landed:
- The two measured probes from the upstream cards flip ALLOWED → blocked in this repo's copy. Run each against the hook before your change (expect ALLOWED) and after (expect blocked), and show both. A selftest count alone does not prove the fail-open closed.
- The retire the unimplemented async-export path (
useExportJob,ExportProgressDialog,DataSource.createExportJob/getExportJobProgress, the export-job type re-exports) — objectstack#17158 ruling A lands after it #10247 mirror twins stay ALLOWED — the port must not over-block. A guard that blocks more than intended is a defect, not extra safety; it would stop other agents from working. - Report the selftest count before your change as well as after.
121 passedmeans nothing unless the reader can see it was 100.
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 (perCLAUDE.md), and if the hook blocks you mid-task, that is a signal to re-read, not to setOS_ALLOW_MAIN_EDITS=1and push through.
Refs: objectstack#11131 · #11133 · #11234 · PR objectstack#11278 · objectui#5459 · #5712.
Generated by Claude Code
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actions{ "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
yinlianghui-tw commented
on Aug 24, 2026 CollaboratorMore actionsos-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
- added a commit that references this issue
on Sep 6, 2026 - added a commit that references this issue
on Sep 9, 2026
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.share 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:
split_segments()gains the backslash branchtokenize()already had — an unquoted\"no longer opens a phantom quote region (closes a fail-open whereecho \" ; sed -i s/a/b/ pkg/x.tswas ALLOWED).strip_heredocs()'s delimiter scan learns the comment rule via astrip_line_comment()helper — a heredoc introducer merely NAMED in a comment no longer swallows the real commands after it.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
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.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).