Skip to content

fix(hooks): read $(…), backticks and ${…} inside "…" as their own quoting context in every Bash guard - #25025

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24901-hook-nested-quote-family
Oct 21, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24901-hook-nested-quote-family

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24901

Clause-②: no

What

Every quote-aware segmenter in .claude/hooks/ now reads a $(…), a backtick or a ${…} met inside a double-quoted word as a quoting context of its own, through one function, subst_end(). A " inside such a body nests; it no longer closes the outer string. Before this, the inner " was read as the closer, the rest of the body became bare shell, and a pure read was blocked.

  • guard-main-checkout-bash.sh: subst_end() (added by PR fix(hooks): read a command substitution inside a double-quoted word as its own quoting context #24886 for $(…) and backticks) now also answers where a quoted ${…} ends, and decides by itself whether the character in front of it opens such a context. Its three call sites (the heredoc comment scan, segmentation, tokenisation) now share that trigger too, so the passes cannot disagree on which characters open a context.
  • guard-shared-stash.sh, guard-process-kill.sh, guard-tree-enum.sh: each carries a byte-identical copy of subst_end(), and split_segments() delegates to it from its in-quote branch. The three split_segments() are now byte-identical as well.
  • guard-tree-enum.sh also gains the in-quote backslash branch (\" inside "…" leaves the region open) that its two siblings already had. Its header already said its pass was "kept case-for-case identical to guard-shared-stash.sh's", but it was not. This is the in-place fix described under Acceptance notes.

Diff: 8 files, +392 / -34, all under .claude/hooks/ (Tier S).

Measurement: every row of the card's enumeration table

On 62bc12930, the dispatch base (pre-fix), and on this PR's head. Exit 2 = blocked, 0 = allowed. Every command below is a pure read.

hook command pre-fix this PR
main-checkout-bash echo "${x:-"a > b"}" 2, target: b} 0
main-checkout-bash echo "${x#"a > b"}" 2, target: b} 0
main-checkout-bash echo "${x:-"a; sed -i s/a/b/ pkg/x.ts"}" 2 0
main-checkout-bash (heredoc scan) a quoted ${x:-" #"} on a heredoc's introducing line 2 (the # read as a comment, so the heredoc body was analysed) 0
shared-stash echo "a $(echo "x; git stash pop")" (card position 1) 2 0
shared-stash echo "a `echo "x; git stash pop"`" 2 0
shared-stash echo "${x:-"a; git stash pop"}" 2 0
process-kill echo "a $(echo "x; pkill -f node")" 2 0
process-kill echo "${x:-"a; pkill -f node"}" 2 0
tree-enum echo "a $(echo "x; ls *.yml")" && git show origin/main:README.md 2 0
tree-enum echo "${x:-"; ls *.yml"}" && git show origin/main:README.md 2 0
tree-enum echo "say \"; ls *.yml; git show origin/main:x\" ok" 2 0

So H4 is answered: both NOT MEASURED rows (guard-process-kill.sh, guard-tree-enum.sh) carried the defect, and both are fixed here rather than pinned clean.

Recall controls, unchanged (each blocked by its own hook before and after): echo hi > out.txt, sed -i "s/a/${x}/" pkg/x.ts, sed -i "s/a/${x:-"b"}/" pkg/x.ts, echo "${HOME}" > pkg/x.ts, echo "${x:-'a'}" ; sed -i s/a/b/ pkg/x.ts, X="${Y:-"z"}" && git stash push -m wip, git stash push -m "${msg:-"wip; x"}", kill $(pgrep -f "${pat:-"node"}"), pkill -f "${pat:-"vitest; x"}", for f in "${dir:-"docs"}"/*.md; do git show "origin/main:$f"; done. Every pre-existing case in all five matrices passes unchanged.

Four fail-open holes close as a side effect, because the outer string no longer hangs open. Each was ALLOWED before and is blocked now: echo "$(printf '"')" ; git stash pop, echo "${x:-$(printf '"')}" ; git stash pop, echo "$(printf '"')" ; pkill -f vitest, and in tree-enum echo "he said \"x\"" ; for f in *.yml; do git show "origin/main:$f"; done.

H2: how one rule lives in four hooks, an identical copy pinned identical

The hooks share no code today. Each one is a standalone script registered on its own in .claude/settings.json. The sharing that exists is by copy: guard-process-kill.sh says its pass is "Copied case-for-case from guard-shared-stash.sh's split_segments()", and guard-tree-enum.sh says its pass is "Kept case-for-case identical". So this PR keeps the copy form and adds the missing half, a pin. Each copying hook's self-test extracts subst_end() (and split_segments() where it is shared) from its hook and from the canonical file, and fails unless the two are byte-identical and non-empty. A sourced helper file was the alternative. It was not chosen, for three reasons:

  1. It would make one file a single point of failure for all four guards at once, the same reason AGENTS.md gives against a shared self-test module.
  2. It would add a failure mode the hooks do not have today: a missing or unreadable helper would leave SUBST_END at 0 inside a for loop, so it would need its own fail-open guard.
  3. It would be a new file for the objectui mirror to carry, while the copies already mirror one-for-one.

The prose claim alone had already drifted: guard-tree-enum.sh lacked the in-quote escape branch while its header called the pass identical. The pin catches exactly that, and it is ablated below.

H3: a rule of its own for ${…}, not failing open

The two routes the card allows, measured on guard-main-checkout-bash.sh:

command (a real write into the primary checkout) pre-fix route A (this PR: track ${…}) route B (a quoted ${ makes the rest inert)
sed -i "s/a/${x}/" pkg/x.ts 2 2 0
sed -i "s/a/${x:-"b"}/" pkg/x.ts 2 2 0
echo "${HOME}" > pkg/x.ts 2 2 0
echo "${x:-"a"}" ; sed -i s/a/b/ pkg/x.ts 2 2 0
echo "${x:-'a'}" ; sed -i s/a/b/ pkg/x.ts 2 2 0
cp "${SRC}" pkg/x.ts 2 2 0

Route B was prototyped as a one-line change, the ${ arm returning "rest inert". It gives up the reflexive in-place edit with a variable in its script, which is what this guard exists for. To narrow it to "only a ${…} whose body holds a "", it would first have to find where the body ends, and that is route A's parse. Route A costs one more closer on the existing stack in subst_end(), about 15 lines in one function that all three passes already call. That is the least new parser surface on which both passes agree.

H3 premise, partly disproved by measurement. The card says a ' inside "${…}" is literal. That holds for the expansion's output, and for bash --posix and dash when finding the extent. It does not hold for bash 5.2's default mode, which reads '…' as a quoted run while looking for the closing brace: x=; echo "${x:-'}'}" prints '}' under bash 5.2 and ''} under bash --posix and dash. In those two, the first } closes the expansion and '} is literal text after it. The two readings agree only when the run holds none of " \ $ } or a backtick. So such a run is skipped, and any other ' there makes the rest of the input one inert run (fail open). That is the header's own contract: an extent that depends on the shell is not one the guard can read with confidence. Both one-reading-only variants were ablated (below), and each goes red.

What route A gives up. The PM asked that a real write the pre-fix hook blocks stay blocked. I measured a 41-command corpus across the four hooks, pre-fix and with this PR. Every write, stash or kill the pre-fix hooks blocked stays blocked, except shapes the pre-fix reached only through the misreading itself:

  • An action nested inside a QUOTED substitution, reached through an inner string: echo "${x:-"$(sed -i s/a/b/ pkg/x.ts)"}", echo "${x:-"$(git stash pop)"}", echo "a $(echo "$(git stash pop)")", and the pkill twins. The pre-fix blocked them only because the inner " exposed the body as bare shell, and the same misreading is what blocked the pure reads above. echo "$(git stash pop)" was allowed before and after. This is the class PR fix(hooks): read a command substitution inside a double-quoted word as its own quoting context #24886 already made a deliberate fail-open in guard-main-checkout-bash.sh ("a command inside a QUOTED $(…)"). The sed shape is pinned in that hook's matrix. In the stash and kill matrices the class is pinned by echo "$(git stash pop)" and echo "$(pkill -f node)".
  • A target that holds an expansion: echo hi > "${x:-"a > b"}". The pre-fix blocked it on the wrong target, b}; it now fails open on its $, which is the header's existing promise.
  • A '…' run holding } or " inside a quoted ${…}: echo "${x:-'}'}" ; sed -i s/a/b/ pkg/x.ts. Bash and dash end the expansion in different places. Its twin echo "${x:-'}"; sed -i s/a/b/ pkg/x.ts'}" is a pure read under bash and was a false block before.

Keeping the first group blocked would mean analysing quoted substitution bodies as commands. That is a new claim with recursive parser surface, and it would diverge from guard-main-checkout-bash.sh's documented fail-open. This PR does not do it, and the report puts it to the seat as an open question.

Boundary, written into the helper's header. An unquoted ${…} holding a ) inside a quoted substitution (echo "$(echo ${x:-a)b})", which bash prints as a)b) ends the body too early. The rest is then read as it was before PR #24886. This is the same direction as the existing case-pattern and comment boundary.

H5: objectui port

This PR changes guard-main-checkout-bash.sh again. subst_end() gains the ${…} arm and the ' rule, and now returns 1 for a non-opener. Its three call sites change from testing two-character prefixes to the shape { [ "$ch" = '$' ] || [ "$ch" = '`' ]; } && subst_end "$s" "$i". objectui#13260, the port of PR #24886 that mirrors this hook case-for-case, should carry this delta too, along with the stash, process-kill and tree-enum copies and their self-test pins. This PR changes no objectui file.

Tests, ablations and gates

Matrices and gates ran at head 1df8c6399. The ablations ran at d83fca2fb, whose eight files are byte-identical to the head's: the merge touched nothing under .claude/hooks/.

Hook matrices (bash .claude/hooks/X.selftest.sh): guard-main-checkout-bash 157 passed / 0 failed (141 on main, +16), guard-shared-stash 65/0 (53, +12), guard-process-kill 80/0 (69, +11), guard-tree-enum 50/0 (38, +12), guard-main-checkout 120/0 (unchanged).

The new self-tests against the pre-fix hooks (each base hook copied beside its new matrix): main-checkout-bash 149 passed / 8 failed, shared-stash 59/6, process-kill 74/6, tree-enum 43/7. In main-checkout-bash the 8 are the ${…} false blocks plus the four documented fail-open pins that the pre-fix blocked. In the three copies they are the false blocks, the fail-open mirrors and the identity pins.

Ablations went through scripts/ablation-replace.mjs in WRAP mode, with the hook's matrix as the child. Each mutation was proven on disk (anchor 1 to 0, blob changed), and each restore was proven by blob equal to HEAD and an empty git diff HEAD:

  • A1 ${ arm removed from subst_end(): 8 red (every ${ form).
  • A2 heredoc comment scan delegation only: 2 red (the $(…) and ${…} heredoc pins).
  • A3 segmentation delegation only: 8 red.
  • A4 tokenisation delegation only: 5 red (including echo "${x:-"a > b"}" and echo "${x#"a > b"}"). So no single pass fixes ${…} alone.
  • A5a bash-only reading of ' (the special-character check deleted): 1 red. A5b POSIX-only reading (the ' arm deleted): 2 red. A5c a ' always fails open: 1 red (echo "${x:-'a'}" ; sed …, the recall case).
  • A6 the stash guard's delegation removed: 5 red. A7 a behaviour-neutral edit to the stash copy of subst_end() (locals reordered): exactly 1 red, the identity pin. A8 the tree-enum in-quote escape branch deleted: 3 red (both \" cases and the split_segments() pin).

Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 16 families, the same 16 as at dispatch. All 16 exited 0, each exit captured before any pipe. --ran printed: "16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN". pnpm check:bash32-floor: "19 constructs checked, floor bash 3.2". check:doc-formula-expressions first answered PREREQUISITE NOT MET (exit 3, unbuilt @objectstack/formula and @objectstack/lint). I built both under the verify lock, and it then exited 0. pnpm check:pm-settings-deny-roster: exit 0. Control-byte self-scan of the eight files: no match.

Cost on a 1.9 KB, 25-line command of the card's probe shape: main-checkout-bash took 146 ms pre-fix and 163 ms with this PR, shared-stash 46 and 53 ms, process-kill 63 and 61 ms, tree-enum 63 and 55 ms.

No changeset: .claude/** publishes nothing (skip-changeset).

Acceptance notes

  • In-place fix under the bounded exemption: the tree-enum in-quote escape branch. It is the same defect class as this card: a " that bash does not read as closing the outer string was read as closing it. The fix is mechanical, a verbatim copy of the branch its two siblings carry, with its shape already pinned in their matrices. The file is inside this card's declared surface, and the change is in the same gate family. Measured before: echo "say \"; ls *.yml; git show origin/main:x\" ok" exit 2 (a pure read), and echo "he said \"x\"" ; for f in *.yml; do git show "origin/main:$f"; done exit 0 (a real sweep let through).
  • guard-shared-stash.sh's header still says "Self-test (51 cases…)". It was 53 on main and is 65 now. That is a stale count in prose, left as is.
  • Merged origin/main (05b28c199, 4 commits, none under .claude/hooks/) before opening, per AGENTS.md §10. The union above was re-run on the merge head.

维护者速读(草稿)

改了什么

修了四个 Bash 守卫钩子的同一类误判:命令里双引号包着的 $(…)、反引号或 ${…} 里如果又带了引号,钩子会把内层引号当成外层字符串的结束,结果把纯只读命令误拦下来(比如 echo "${x:-"a > b"}" 被当成写文件)。现在四个钩子共用同一个判断函数,各自持有一份逐字节相同的副本,并由自测锁定"副本必须与原件相同"。顺带补上 guard-tree-enum.sh 漏掉的一条转义引号规则,它的注释早就声称与兄弟钩子一致,实际并不一致。

为什么改

这是 #24812 那次修复的同族收尾:上次只修了写保护钩子里的 $(…),stash、杀进程、目录枚举三个钩子和 ${…} 写法仍会误拦只读命令。钩子头部承诺"看不懂的一律放行,绝不误拦只读命令",误拦违背这个承诺,会诱使人把钩子关掉。

风险与代价(含回滚)

真正的写入、stash、按名杀进程、目录枚举误用仍全部拦截(原有用例全部通过)。代价:少数原本"碰巧被拦"的形状现在放行,即藏在双引号子命令里的写操作,以及 bash 与 POSIX shell 对引号理解不一致的极端写法;这与上次修复时接受的取舍一致,并已写入自测。回滚就是 revert 本 PR,只涉及 .claude/hooks/ 下 8 个文件。

席位意见

你要做的

无需操作:这是 Tier S,席位按契约档复核后自行落地。objectui 的同名钩子由另一张卡(objectui#13260)跟进,本 PR 不改 objectui。


Generated by Claude Code

…wn quoting context in every Bash guard

guard-main-checkout-bash.sh's subst_end() now also answers where a ${...}
met inside a double-quoted word ends, and decides itself whether the
character opens such a context. guard-shared-stash.sh,
guard-process-kill.sh and guard-tree-enum.sh carry a byte-identical copy
and delegate to it from split_segments()'s in-quote branch.
guard-tree-enum.sh also gains the in-quote backslash branch its two
siblings already had, so the three simple segmenters are identical.

Claude-Session: https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L
Co-authored-by: Claude <noreply@anthropic.com>
… byte for byte

Each Bash guard's matrix gains the measured false blocks for $(...),
backticks and ${...} inside "...", the recall controls next to them, and
the fail-open mirrors the rule closes. The three copying guards also
assert that their subst_end() equals guard-main-checkout-bash.sh's and,
for guard-process-kill.sh and guard-tree-enum.sh, that their
split_segments() equals guard-shared-stash.sh's.

Claude-Session: https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 21, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1df8c6399a8dfa70219b9278d2346d88ef6581b9
Local-runs: none
Scope: .claude/hooks/guard-main-checkout-bash.selftest.sh, .claude/hooks/guard-main-checkout-bash.sh, .claude/hooks/guard-process-kill.selftest.sh, .claude/hooks/guard-process-kill.sh, .claude/hooks/guard-shared-stash.selftest.sh, .claude/hooks/guard-shared-stash.sh, .claude/hooks/guard-tree-enum.selftest.sh, .claude/hooks/guard-tree-enum.sh + .claude/agents/os-dev.md, .github/workflows/lint.yml

Face: rules (.claude/**, Tier S). Inputs: card #24901's body and both comments (the Claim and the os-dev report), PR #25025's body, file list and diff against main, the check-runs on the head, and the eight files read at the head and at origin/main as git objects. The head sha equals the PR's head.sha; the diff is eight files, +392 / -34, all under .claude/hooks/, no changeset file. The two cited files are the bounded in-place exemption (os-dev.md, the four conditions and two obligations under rule 3) and the gate that enforces the pins (lint.yml's lint-repo-tooling job: the find-discovered hook self-test step and pnpm check:bash32-floor, folded into the job by its Lane verdict step).

① Derived judgments

The accept set here is which Bash commands each PreToolUse guard allows (exit 0) and blocks (exit 2), under guard-main-checkout-bash.sh's header contract: precision over recall, fail open on what cannot be read with confidence. Each verdict change the diff implies, read in the code:

  1. False blocks removed — right. Inside "…", a $(…), a backtick or a ${…} is now one quoting context, so an inner " nests. The card's two measured positions (stash: echo "a $(echo "x; git stash pop")"; main-checkout-bash: a quoted ${…} default holding a redirect arrow, which the pre-fix tokenised as the target b}) and the analogous shapes in process-kill and tree-enum are pure reads and now exit 0. Mechanism verified: subst_end() opens on $(, ${ or a backtick and returns 1 on anything else, and the three canonical passes (strip_line_comment, split_segments, tokenize) use one identical trigger, { [ "$ch" = '$' ] || [ "$ch" = '' ]; } && subst_end`, so segmentation, tokenisation and the heredoc introducer scan cannot disagree on which character opens a context — the card's "one shared rule per shape". The two rows the card left NOT MEASURED (process-kill, tree-enum) are both fixed, with a case per row.

  2. Recall kept — right. A real write, stash, name-matched kill or working-tree sweep that sits behind, beside or around a quoted expansion still blocks: the body is kept verbatim inside the word, tokenize() fails open on a target holding $ (line 418, the pre-existing promise), and a stash or kill head word outside the string is still its own segment. The matrices pin these (sed -i "s/a/${x:-"b"}/" pkg/x.ts, echo "${x:-"a"}" ; git stash pop, kill $(pgrep -f "${pat:-"node"}"), for f in "${dir:-"docs"}"/*.md; do git show "origin/main:$f"; done), and every pre-existing case in all five matrices is unchanged and green in the head's self-test step.

  3. New blocks, four fail-open holes closed — right. echo "$(printf '"')" ; git stash pop, its ${…} twin, the pkill -f vitest twin, and in tree-enum echo "he said \"x\"" ; for f in *.yml; do git show "origin/main:$f"; done are real actions the pre-fix let through because a misread " left the outer string hanging. Blocking them is the contract's own direction; each is pinned.

  4. Real actions now allowed, class A: an action nested inside a QUOTED substitution and reached only through an inner string (echo "${x:-"$(git stash pop)"}", echo "a $(echo "$(git stash pop)")", the sed -i and pkill twins) — right. The pre-fix blocked these only because the inner " exposed the body as bare shell, the very misreading that produced the false blocks. echo "$(git stash pop)", echo "$(pkill -f node)" and echo "$(sed -i s/a/b/ pkg/x.ts)" were allowed before and after in every hook, so the class was already a fail-open, declared in the canonical header since the guard-main-checkout-bash.sh reads quotes inside a $(…) command substitution as closing the outer double quote, so a > inside a quoted curl body is taken for a redirect and a read-only command from the primary checkout is blocked #24812 fix ("a command inside a QUOTED $(…)"). The three copies inherit the same declared hole, and each matrix pins the class.

  5. Real actions now allowed, class B: a target holding an expansion (echo hi redirected into a quoted ${…} default) — right. The pre-fix blocked on the wrong target, b}; the header has always promised fail-open on a target holding $.

  6. Real actions now allowed, class C: a ' run inside a quoted ${…} that holds ", \, $, } or a backtick — right as a declared hole, wrong as the PR body classifies it. Verified in subst_end(): under a } top, a ' run holding none of those characters is skipped (the bash reading and the POSIX reading reach the same place, which I confirmed by walking both readings), and any other ' makes the rest one inert run. The hole is written into the canonical header's "Fails open, deliberately, on" list and pinned by two matrix cases. But the PR body files echo "${x:-'}'}" ; sed -i s/a/b/ pkg/x.ts under "shapes the pre-fix reached only through the misreading itself", and that is not so: on that shape the pre-fix, bash 5.2 and dash all close the outer string at the same " (the shells disagree on where the EXPANSION ends, not on where the STRING ends), so the pre-fix block was a correct reading, and this is a recall regression on a real write under every shell. It is bounded — a quoted parameter default holding a ' run with a brace, followed by a write, is not the reflexive edit the guard targets — and the dev's own A5a ablation shows that taking bash's reading of such a run outright (the shell the Bash tool runs) passes every other case and costs exactly this one pin, which is the cheaper rule. Accepted as a declared hole under the header's own contract; the misclassification is a defect in the PR body's prose, not in the tree. Escalated in ③.

  7. The byte-identical copies and their pins — right. Read, not run: at the head the three subst_end() copies equal the canonical byte for byte, and split_segments() in process-kill and tree-enum equal the stash guard's; at origin/main tree-enum's lacked the six-line in-quote backslash branch while its header called it identical, which verifies the drift the dev names. The pin catches drift: fn_text is a sed range from the NAME() { line to the first line that is exactly }, and same_fn requires a non-empty extract whose first line is that header line and byte equality with the canonical's extract; neither function has an interior line that is exactly }, each header line occurs once per hook, and a missing or renamed canonical yields an empty theirs, which fails the equality. Every copying hook pins subst_end() against guard-main-checkout-bash.sh, and process-kill and tree-enum pin split_segments() against guard-shared-stash.sh, so a change to either canonical reds the copying matrices and a change to a copy reds its own. The gate that runs them is the Claude hook guard self-tests step of lint-repo-tooling, discovered by find so a new matrix is picked up with no edit, folded into the job by its Lane verdict step, and carried by the required Lint & Repo Gates context — green on this head. The canonical's own split_segments() sits outside the three-way pin by its pre-existing word bookkeeping; that is the existing shape, not drift. A copy over a sourced helper is the right call: the hooks are standalone and already share by copy, and a helper would be one point of failure for four guards, the reason AGENTS.md gives against a shared self-test module.

  8. The tree-enum in-place fix against os-dev's bounded in-place exemption — clears all four conditions and both obligations. ① same defect class: a " that bash does not read as closing the outer string, read as closing it — and the byte-identity the card's own Done-when asks for cannot hold without this branch; ② mechanical, a verbatim copy of the branch its two siblings carry, the shape already pinned in their matrices; ③ no other claim holds the file: the Claim's file surface names guard-tree-enum.sh and its selftest, no other open PR touched .claude/hooks/** at dispatch, and the head's No other open PR may claim the same single-writer path check is green; ④ same gate family (the self-test step and check:bash32-floor), no new verification surface. Obligations: the declared file surface already covered the file, and the PR body names the fix with before and after measurements under Acceptance notes. Right.

  9. Boundary the dev documents — right. An unquoted ${…} holding a ) inside a quoted substitution ends the body early and the rest reads as it did before the guard-main-checkout-bash.sh reads quotes inside a $(…) command substitution as closing the outer double quote, so a > inside a quoted curl body is taken for a redirect and a read-only command from the primary checkout is blocked #24812 fix; the same direction as the existing case-pattern and comment boundary.

Public surface: none. .claude/** is a governed path, nothing published changes, no settings or workflow edit, and the four hooks stay registered in .claude/settings.json as before.

② Semver level

skip-changeset is right: the diff publishes nothing from any released package (eight files under .claude/hooks/, no changeset file, Check Changeset green). Clause-②: no on the PR body matches — no published schema's accept set widens or narrows. Nothing to carry into a CHANGELOG.

③ Boundary flags

  • open_questions (accept the documented fail-open for quoted bodies, or analyse them as commands): A. Read in the code and the matrices: the class was a declared fail-open in every hook before this PR (echo "$(git stash pop)" and its twins allowed throughout), the pre-fix blocks inside it were accidents of the same misreading the card fixes, and claiming quoted bodies would be a new recursive-segmentation claim that diverges from the canonical header. Precision over recall prefers A.
  • Dev deviation 1 (shapes the pre-fix blocked that are now allowed): accepted for classes A and B; class C escalated. The '}'} shape is a real write under every shell, blocked correctly before and allowed now; it is declared in the header's hole list and pinned, so it lands as a chosen hole. For the seat to carry as a follow-up on this family (noted, not filed — this record cuts no card): subst_end() could take bash's reading of a ' run inside a quoted ${…} outright, since the Bash tool executes bash; the dev's A5a ablation measures that at one red, this pin. The PR body's prose classing the shape as "reached only through the misreading" is wrong and should not be copied into the objectui port's rationale.
  • Dev deviation 2 (tree-enum in-place fix): accepted, ① point 8.
  • Dev deviation 3 (subst_end() owns its trigger; the three call sites changed shape): accepted; the PR body's H5 section names the delta for objectui#13260, and the card splits the objectui twins by repository.
  • Dev deviations 4 to 6 (merged origin/main before opening; model-free commit trailers; label verified by a REST read): accepted, each matches AGENTS.md; skip-changeset is on the PR.
  • Acceptance note, stale header count: guard-shared-stash.sh still says "Self-test (51 cases…)" against 65 now. Prose that no gate reads; accepted, a one-line follow-up for whoever next touches the file.
  • Gates on the head, read 2026-10-21T01:44Z: 26 success, 14 skipped, none in progress or failed. Required contexts: Lint & Repo Gates success (in progress at my first read, concluded before this record), TypeScript Type Check success, Test Core success, Governed Surface Queue Guard success, Build Core skipped — the paths filter's answer for a diff with no package, reported as it stands. Lint · repo & tooling gates, which carries the hook self-test step and check:bash32-floor, is success; Check Changeset, Dogfood Regression Gate and the three claim-parity checks are success.
  • Not re-verified locally, by design: the dev's ablations A1 to A8 and the pre-fix measurements are the dev's evidence. What this record verifies is the mechanism in the code, the byte identity of the copies, the soundness of the pins, and the gate verdicts as the check-runs report them.

Implemented-by: claude/issue-24901-hook-nested-quote-family
Reviewed-by: session_01M82feuetLzrCiBz41BJb7L

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills seat 1 (seat post #7623) · session_01M82feuetLzrCiBz41BJb7L · head 1df8c6399a8dfa70219b9278d2346d88ef6581b9 · 2026-10-21T01:50Z

  1. Form. The PR is a draft against main, and the body's first line is Fixes #24901. Assignee huangyiirene equals the card's. Files: the eight .claude/hooks/** files the claim declared (+392/−34). check-governed-merges.mjs --pr 25025: Tier S, 426 changed lines. skip-changeset is right: nothing published.
  2. Premises measured. Every row of the card's enumeration table is resolved: both measured positions are fixed, and both NOT MEASURED rows (guard-process-kill.sh, guard-tree-enum.sh) were measured defective and are fixed, each with a case. The shared rule is subst_end(); its three byte-identical copies are pinned by each copying hook's self-test (review ①.7).
  3. Gate check-runs, pinned to this head (2026-10-21T01:44Z, per the review): 26 success, 14 skipped, 0 failures. Lint & Repo Gates carries the hook self-test step and check:bash32-floor, and is success. check-expected-skips.mjs --pr 25025, from a checkout pinned to origin/main 73432299c: OK — 14 skipped check-run(s), every one in main's roster (exit 0). The contract review of record is comment 6306009730 (Served-tier: CONTRACT_REVIEW_TIER, this head, Local-runs: none, PASS), rendered by an isolated subagent at that tier and adopted by this seat.
  4. Accepted deviations and the dev's open question.
  5. Escalated by the review, and how this seat carries each. Neither is a card; both go to the seat post's carried pointers.
    • Class C (review ①.6): a ' run inside a quoted ${…} that holds ", \, $, } or a backtick is now a declared, pinned fail-open, and a real write after it (echo "${x:-'}'}" ; sed -i …) is allowed. The pre-fix read that shape correctly, so this is a recall regression on a bounded shape, not the reflexive edit the guard targets. It is accepted under the header's precision-over-recall contract. The cheaper rule, bash's reading of the run outright (the dev's A5a: one pin flips), is carried as a pointer for the family's next edit. The PR body's prose calls this shape "reached only through the misreading"; that is wrong, and this seat flags it for the objectui port instead of rewriting the body.
    • The stale header count in guard-shared-stash.sh (51 cases, now 65) is prose read by no gate. It is fixed at the file's next edit.
  6. Landing. Tier S with an at-tier PASS on this head and every check green: the seat flips it ready, arms auto-merge once mergeable_state settles, follows it to MERGED, then closes out guard hooks: guard-shared-stash.sh still reads a nested quote inside "$(…)" as closing the outer string, and guard-main-checkout-bash.sh does the same for "${x:-"…"}" — the #24812 family close-out #24901.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Director record · governed landing recognised · summon #45 continuation 3, session_01Lg6GQ4xjqmvEzYv78d6H7i · 2026-10-21T05:04Z

Audit window --since-ref objectstack=e4d604efd39f (tip eddffc3390cf): this PR landed on the Tier S surface .claude/hooks/** (×8, +392/−34: the four Bash guards and their selftests) at 338297e46, merged by objectstack-fleet[bot] from the queue, with the at-tier contract review PASS 6306009730 (Served-tier: CONTRACT_REVIEW_TIER, head 1df8c6399a) and the domain:skills seat 1 ACCEPT 6306025148 on the same head, and no APPROVED review by a GOVERNED_APPROVERS account — one of the three rows this window puts to the maintainer under the rule of #24241.

Put to the maintainer in this session's chat beside batch #381, with one line on what changed: the fix PR #24886 made to the main-checkout guard now reaches all four Bash guards — a $(…), a backtick or a ${…} met inside a double-quoted word is read as its own quoting context to its end through one shared subst_end(), the three split_segments() are byte-identical, and guard-tree-enum.sh gains the in-quote backslash branch its siblings had — so a read-only command is no longer blocked by a quote inside a substitution. The maintainer answered 「其他同意」 — recognised. Nothing is owed on this PR; the ledger block on #12708 carries the row.

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

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants