You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] check-shard-attestation's invokesScript() reads an echo QUOTING the invocation as an invocation — adjacency without quote-awareness #10889
Noticed while implementing #10877 (PR #10884), which needed a narrow "does this step actually
INVOKE the script?" recognizer and evaluated invokesScript() as the candidate to reuse.
What it is
scripts/check-shard-attestation.mjs's invokesScript(runText, flag) added adjacency to
fix #6589's false red (a bare --verify substring anywhere in a run: block). Adjacency is
the right idea, but the tokenizer under it splits on whitespace and then strips the quotes off
each piece:
Shell does not word-split inside quotes. So a quoted string being handed to echo is torn
into separate "tokens", and the script name plus its flag inside that string read exactly like
a program being run with an argument.
Measured, on this tree
Against the real exported invokesScript (worktree at c20069e17f):
true | genuine invocation node scripts/check-shard-attestation.mjs --verify --dir x
true | an ECHO merely quoting it echo "node scripts/check-shard-attestation.mjs --verify is how you would check"
false | a ::error:: annotation echo "::error::check-shard-attestation.mjs --verify exited 1"
The second line is the finding. The third is green only incidentally — the ::error:: prefix
happens to fuse onto the basename token so it matches neither === SCRIPT_BASENAME nor endsWith('/' + SCRIPT_BASENAME). Write the same annotation with a space, or as echo "run node scripts/check-shard-attestation.mjs --verify to reproduce", and it counts.
Why it matters, and its direction
invokesScript feeds presence questions — whether an attesting job ends with the --emit pair, whether an aggregate gate runs --verify. Presence assertions fail toward a
silent green, so the symptom is the gate reporting that a leg attests when it does not.
⚠️ The trigger is the ordinary act of writing a diagnostic about the script, which is the
same #4890 shape the neighbouring ci.yml block already walked into for the sibling flag —
and the reason #10818/#10877 exist at all. This is the same family, one file over.
Not urgent and not currently mis-firing on the checked-in tree: no workflow presently
echoes the invocation with a spelling that matches. It is latent, exactly as #10818 was.
Prior art for the fix, already in the tree
#10884 solved the same problem for check-required-contexts.mjs without touching this file, by
splitting commands with a word-splitter that records which words came out of a quoted
region and requiring the program word to be unquoted (arguments may be quoted freely —
refusing "--verify" would narrow a presence assertion into a false red). See commandWords()
and invokes() there.
Whether this file should adopt that, or the two should converge on one shared helper, is a
judgment call rather than a mechanical swap — and ⚠️#10628 already had to undo one
mirrored helper in a neighbouring file, so "extract a shared module" is not automatically the
right answer here.
Filed unassigned, per scope discipline — #10884 deliberately did not touch this file.
Triage (first-touch grading): promoted finding → pm:queue, kept tooling + domain:devx, type Task. Rationale: measured false-positive with a live repro table (an echo quoting the invocation reads as an invocation), failure direction is silent green on a presence assertion, and the trigger is the ordinary act of writing a diagnostic — the same family as #10818/#10877, latent exactly the way #10818 was before it fired. Prior art for the fix is already merged in check-required-contexts.mjs (#10884's commandWords()/quote-region discipline). The card's open question (adopt vs converge on a shared helper) is dev judgment with the #10628 caution noted — not a decision-box matter.
Dispatch notes: serialize after PR #10884 (in-flight for #10877) lands so the prior art is on main to reference; single-file fix in scripts/check-shard-attestation.mjs + self-test cases for the quoted-echo shape in both directions. Size/model suggestion: S–M.
The premise is independently confirmed. This seat read invokesScript() on origin/main while reviewing PR #10884 and reached the same reading the card records: SCRIPT_BASENAME is a module-level const, the tokenizer is command.split(/\s+/) followed by token.replace(/^['"]|['"]$/g, ''), and the flag is then looked for among the tokens after the basename. Nothing in that path knows a quoted string is one word.
The prior art the card points at is now on main. PR #10884 merged at 16:15:39Z, so commandWords() and invokes() in scripts/check-required-contexts.mjs are checked in and readable — including their design note on why the program word must be unquoted while arguments may be quoted freely (refusing a quoted --verify would turn a presence assertion into a false red).
⚠️ That is the remedy's shape, not necessarily its packaging. Whether to extract those helpers to a shared module, or re-derive them here, is the agent's call and is the thing this PR will be reviewed on. check-shard-attestation.mjs is a sibling of the file that owns them, not a consumer of it today.
Direction of failure, restated because it decides the test design:invokesScript feeds presence questions, and presence assertions fail toward a silent green. A fix that cannot be shown to red on the echo case has not been shown to work.
If an earlier claim comment with a different session ID appears above this one, that claim wins and this one is void.
{
"issue": 10889,
"status": "done",
"branch": "claude/issue-10889-invokes-script-quote-aware",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10904",
"premise_still_valid": true,
"summary": "PACKAGING DECISION: shared, not re-derived -- and the deciding fact is mechanical rather than aesthetic. check-required-contexts.mjs already imports shellCommands FROM check-shard-attestation.mjs, so the single import edge between the two files points required-contexts -> shard-attestation. A helper kept in the importer (where #10884 put commandWords/invokes) can never be reached from the importee without a cycle, so 'adopt the sibling's helper' means moving it DOWN to the lexer whose output it consumes. commandWords and invokes now sit beside shellCommands (commands out of a run: block, then words out of a command -- one two-layer lexer, one home) and check-required-contexts imports them over the edge that already existed. Rejected: local re-derivation (that is the hand-mirror #10628 had to undo, and the obstacle that justified the mirror there -- a module running its gate at load time -- does not exist here, since this file has been behind isEntrypoint() all along) and a new scripts/shell-words.mjs (a third home for a lexer whose lower layer is already exported from and imported out of this file; keeping the layers together would have meant moving shellCommands too). CORRECTION TO THE CARD, because it moves the decision: the card cites #10628 as a caution that 'extract a shared module is not automatically the right answer'. Read directly, #10628 DELETED a hand-mirrored helper and replaced it with a shared import after measuring the copies textually and behaviourally identical -- it is evidence against MIRRORING and argues FOR sharing wherever the import is safe, which here it demonstrably is. THE LIVE VERDICT DID NOT MOVE: the real pass is byte-identical before and after ('check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).'), exactly as the card predicted -- latent, not mis-firing. Confirmed independently: the only two mentions of the script in ci.yml outside its real invocations are comments, which shellCommands already drops. The fix makes invokesScript require the PROGRAM word to be unquoted while arguments may be quoted freely; the self-test grew 92 -> 103 assertions and pins the finding, the ::error:: annotation written WITH A SPACE (the card's fragile case -- the fused spelling was green only incidentally), the --emit direction of the same defect, both asymmetry limbs, and a quoted PROGRAM word. Every #6589 pin is untouched and green.",
"tests": "All measurements on final HEAD b9dec72600 (merge-base 145ba756d of origin/main, three-dot). Exit codes captured before any pipe (cmd > log 2>&1; ec=$?), never through tail. NEGATIVE CONTROL FIRST, against the CHECKED-IN invokesScript on origin/main, before any edit: 3 of 8 cases disagreed with the intended contract -- 'MISS | got=true want=false | ECHO quoting it (THE FINDING)', 'MISS | got=true want=false | ::error:: WITH A SPACE', 'MISS | got=true want=false | echo \"run node ... to reproduce\"'; the fused ::error:: case was already false (incidentally) and the #6589 cases were already correct. After the fix the same probe reports 'PROBE VERDICT: 0 case(s) disagree'. GATE VERDICTS, quoted from each tool's own verdict line. Before: 'check-shard-attestation --self-test: 92 assertions (... + the #6589 classifier pins).' exit 0 / 'check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).' exit 0. After: 'check-shard-attestation --self-test: 103 assertions (... + the #6589 classifier pins + the #10889 quoting pins).' exit 0 / the real-pass line UNCHANGED, byte-identical, exit 0. UNION, gate set derived by `node scripts/pm/dispatch-gates.mjs` with NO hand-made path list (it read the merge base itself: 2 paths). All 9 matched families green: check:shard-attestation, check:required-contexts ('150 assertions', '6 required context name(s) pinned across 2 workflow(s)'), check:entry-guard ('130 scripts/ file(s) -- every entry guard goes through invoked-as.mjs; 88 export bindings, 78 of them inert on import'), check:parse-guard ('129 scripts/ file(s)'), check:cross-package-test-inputs ('All 104 self-test cases passed.'), check-ci-filter-parity ('OK: all 82 declared cross-package glob(s) (71 unique) are covered'), plus the three direct `node scripts/...` invocations from ci.yml / required-set-patrol.yml. Also check:nul-bytes ('OK (scanned 6300 text file(s) ... no raw ASCII control bytes)') and a manual grep -naP control-byte scan of both edited files (no matches). eslint --no-inline-config on both changed files: exit 0. #10853 negative control exercised: `pnpm --filter @objectstack/definitely-not-a-package test` printed 'No projects matched the filters' and EXITED 0 -- and it cannot have greened anything here, because the verification path above uses no --filter at all. ABLATIONS -- prediction written to a file BEFORE each run; both self-tests collect failures rather than aborting, so each prediction names the FULL failing set. No dist/ is involved (these are .mjs scripts node runs directly), so there is no rebuild leg; every mutation was proven ON DISK by grep -c of the injected AND the removed text. A first attempt with `perl -0pi` was a silent NO-OP (exit 0, counts unchanged) and was caught by exactly that count, then redone with a python literal replace that asserts the anchor matches exactly once. Every restore leg proven byte-identical with `git diff --quiet` (exit 0) plus a marker re-count. (A) drop the unquoted-PROGRAM condition only -- predicted, non-obviously, that the two halves are INDEPENDENTLY load-bearing and this is the narrower half (once a quoted region is one word the echo blobs no longer resemble the basename at all), so: EXACTLY ONE failure in shard-attestation and GREEN in required-contexts. Observed exactly that: '1 failure(s) -- #10889: a QUOTED program word is a string being passed to something else -- here, to `echo`', and required-contexts '150 assertions' green. (B) revert commandWords to the pre-#10889 tokenizer (the defect itself) -- predicted 8 named failures in shard-attestation, that the fused ::error:: pin and BOTH asymmetry pins would NOT fail, and critically that required-contexts would ALSO redden (the proof the helper is genuinely shared, not a leftover local copy). Observed exactly that: '8 failure(s)', the eight predicted messages, those exclusions holding, and required-contexts '2 failure(s) -- recognizer: a step that only ECHOES --verify-required-set does not INVOKE it ... (#10877)' and 'wiring: an `echo` QUOTING the invocation is prose ... (#10877)'. (C) delete the shared import -- predicted loud, never silent. Observed 'ReferenceError: invokes is not defined' at check-required-contexts.mjs:763, exit 1.",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
huangyiirene commentedon Aug 21, 2026
Triage (first-touch grading): promoted
finding→pm:queue, kepttooling+domain:devx, type Task. Rationale: measured false-positive with a live repro table (anechoquoting the invocation reads as an invocation), failure direction is silent green on a presence assertion, and the trigger is the ordinary act of writing a diagnostic — the same family as #10818/#10877, latent exactly the way #10818 was before it fired. Prior art for the fix is already merged incheck-required-contexts.mjs(#10884'scommandWords()/quote-region discipline). The card's open question (adopt vs converge on a shared helper) is dev judgment with the #10628 caution noted — not a decision-box matter.Dispatch notes: serialize after PR #10884 (in-flight for #10877) lands so the prior art is on main to reference; single-file fix in
scripts/check-shard-attestation.mjs+ self-test cases for the quoted-echo shape in both directions. Size/model suggestion: S–M.Generated by Claude Code
claude commentedon Aug 21, 2026
PM claim — dispatching now.
c970724d-303c-5614-9d20-a3f92205cfadclaude/issue-10889-invokes-script-quote-awaredomain:devxThe premise is independently confirmed. This seat read
invokesScript()onorigin/mainwhile reviewing PR #10884 and reached the same reading the card records:SCRIPT_BASENAMEis a module-level const, the tokenizer iscommand.split(/\s+/)followed bytoken.replace(/^['"]|['"]$/g, ''), and the flag is then looked for among the tokens after the basename. Nothing in that path knows a quoted string is one word.The prior art the card points at is now on
main. PR #10884 merged at 16:15:39Z, socommandWords()andinvokes()inscripts/check-required-contexts.mjsare checked in and readable — including their design note on why the program word must be unquoted while arguments may be quoted freely (refusing a quoted--verifywould turn a presence assertion into a false red).check-shard-attestation.mjsis a sibling of the file that owns them, not a consumer of it today.Direction of failure, restated because it decides the test design:
invokesScriptfeeds presence questions, and presence assertions fail toward a silent green. A fix that cannot be shown to red on the echo case has not been shown to work.If an earlier claim comment with a different session ID appears above this one, that claim wins and this one is void.
Generated by Claude Code
fix(devx): read a quoted blob as an argument, not as a program being …
claude commentedon Aug 21, 2026
os-dev-report
{ "issue": 10889, "status": "done", "branch": "claude/issue-10889-invokes-script-quote-aware", "pr": "https://github.com/objectstack-ai/objectstack/pull/10904", "premise_still_valid": true, "summary": "PACKAGING DECISION: shared, not re-derived -- and the deciding fact is mechanical rather than aesthetic. check-required-contexts.mjs already imports shellCommands FROM check-shard-attestation.mjs, so the single import edge between the two files points required-contexts -> shard-attestation. A helper kept in the importer (where #10884 put commandWords/invokes) can never be reached from the importee without a cycle, so 'adopt the sibling's helper' means moving it DOWN to the lexer whose output it consumes. commandWords and invokes now sit beside shellCommands (commands out of a run: block, then words out of a command -- one two-layer lexer, one home) and check-required-contexts imports them over the edge that already existed. Rejected: local re-derivation (that is the hand-mirror #10628 had to undo, and the obstacle that justified the mirror there -- a module running its gate at load time -- does not exist here, since this file has been behind isEntrypoint() all along) and a new scripts/shell-words.mjs (a third home for a lexer whose lower layer is already exported from and imported out of this file; keeping the layers together would have meant moving shellCommands too). CORRECTION TO THE CARD, because it moves the decision: the card cites #10628 as a caution that 'extract a shared module is not automatically the right answer'. Read directly, #10628 DELETED a hand-mirrored helper and replaced it with a shared import after measuring the copies textually and behaviourally identical -- it is evidence against MIRRORING and argues FOR sharing wherever the import is safe, which here it demonstrably is. THE LIVE VERDICT DID NOT MOVE: the real pass is byte-identical before and after ('check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).'), exactly as the card predicted -- latent, not mis-firing. Confirmed independently: the only two mentions of the script in ci.yml outside its real invocations are comments, which shellCommands already drops. The fix makes invokesScript require the PROGRAM word to be unquoted while arguments may be quoted freely; the self-test grew 92 -> 103 assertions and pins the finding, the ::error:: annotation written WITH A SPACE (the card's fragile case -- the fused spelling was green only incidentally), the --emit direction of the same defect, both asymmetry limbs, and a quoted PROGRAM word. Every #6589 pin is untouched and green.", "tests": "All measurements on final HEAD b9dec72600 (merge-base 145ba756d of origin/main, three-dot). Exit codes captured before any pipe (cmd > log 2>&1; ec=$?), never through tail. NEGATIVE CONTROL FIRST, against the CHECKED-IN invokesScript on origin/main, before any edit: 3 of 8 cases disagreed with the intended contract -- 'MISS | got=true want=false | ECHO quoting it (THE FINDING)', 'MISS | got=true want=false | ::error:: WITH A SPACE', 'MISS | got=true want=false | echo \"run node ... to reproduce\"'; the fused ::error:: case was already false (incidentally) and the #6589 cases were already correct. After the fix the same probe reports 'PROBE VERDICT: 0 case(s) disagree'. GATE VERDICTS, quoted from each tool's own verdict line. Before: 'check-shard-attestation --self-test: 92 assertions (... + the #6589 classifier pins).' exit 0 / 'check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).' exit 0. After: 'check-shard-attestation --self-test: 103 assertions (... + the #6589 classifier pins + the #10889 quoting pins).' exit 0 / the real-pass line UNCHANGED, byte-identical, exit 0. UNION, gate set derived by `node scripts/pm/dispatch-gates.mjs` with NO hand-made path list (it read the merge base itself: 2 paths). All 9 matched families green: check:shard-attestation, check:required-contexts ('150 assertions', '6 required context name(s) pinned across 2 workflow(s)'), check:entry-guard ('130 scripts/ file(s) -- every entry guard goes through invoked-as.mjs; 88 export bindings, 78 of them inert on import'), check:parse-guard ('129 scripts/ file(s)'), check:cross-package-test-inputs ('All 104 self-test cases passed.'), check-ci-filter-parity ('OK: all 82 declared cross-package glob(s) (71 unique) are covered'), plus the three direct `node scripts/...` invocations from ci.yml / required-set-patrol.yml. Also check:nul-bytes ('OK (scanned 6300 text file(s) ... no raw ASCII control bytes)') and a manual grep -naP control-byte scan of both edited files (no matches). eslint --no-inline-config on both changed files: exit 0. #10853 negative control exercised: `pnpm --filter @objectstack/definitely-not-a-package test` printed 'No projects matched the filters' and EXITED 0 -- and it cannot have greened anything here, because the verification path above uses no --filter at all. ABLATIONS -- prediction written to a file BEFORE each run; both self-tests collect failures rather than aborting, so each prediction names the FULL failing set. No dist/ is involved (these are .mjs scripts node runs directly), so there is no rebuild leg; every mutation was proven ON DISK by grep -c of the injected AND the removed text. A first attempt with `perl -0pi` was a silent NO-OP (exit 0, counts unchanged) and was caught by exactly that count, then redone with a python literal replace that asserts the anchor matches exactly once. Every restore leg proven byte-identical with `git diff --quiet` (exit 0) plus a marker re-count. (A) drop the unquoted-PROGRAM condition only -- predicted, non-obviously, that the two halves are INDEPENDENTLY load-bearing and this is the narrower half (once a quoted region is one word the echo blobs no longer resemble the basename at all), so: EXACTLY ONE failure in shard-attestation and GREEN in required-contexts. Observed exactly that: '1 failure(s) -- #10889: a QUOTED program word is a string being passed to something else -- here, to `echo`', and required-contexts '150 assertions' green. (B) revert commandWords to the pre-#10889 tokenizer (the defect itself) -- predicted 8 named failures in shard-attestation, that the fused ::error:: pin and BOTH asymmetry pins would NOT fail, and critically that required-contexts would ALSO redden (the proof the helper is genuinely shared, not a leftover local copy). Observed exactly that: '8 failure(s)', the eight predicted messages, those exclusions holding, and required-contexts '2 failure(s) -- recognizer: a step that only ECHOES --verify-required-set does not INVOKE it ... (#10877)' and 'wiring: an `echo` QUOTING the invocation is prose ... (#10877)'. (C) delete the shared import -- predicted loud, never silent. Observed 'ReferenceError: invokes is not defined' at check-required-contexts.mjs:763, exit 1.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
fix(devx): read a quoted blob as an argument, not as a program being …