Uh oh!
There was an error while loading. Please reload this page.
fix(devx): read a quoted blob as an argument, not as a program being run (#10889) - #10904
Merged
Merged
Conversation
…run (#10889) `invokesScript()` in check-shard-attestation added adjacency for #6589 but tokenized with `command.split(/\s+/)` plus a per-token quote strip, so a quoted string handed to `echo` was torn into words and the script name plus its flag INSIDE that string read as a genuine invocation. The reading is now the quote-aware one #10884 built for the sibling gate, moved to the lexer whose output it consumes rather than mirrored: `commandWords` and `invokes` live beside `shellCommands` in check-shard-attestation, and check-required-contexts imports them over the one import edge that already existed between the two files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
marked this pull request as ready for review
August 22, 2026 03:54
Uh oh!
There was an error while loading. Please reload this page.
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32550290075 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#10889
invokesScript()gained adjacency for #6589, but the tokenizer under it wascommand.split(/\s+/)followed bytoken.replace(/^['"]|['"]$/g, ''). Shell does notword-split inside quotes, so a quoted string handed to
echowas torn into pieces and thescript name plus its flag inside that string read exactly like a program being run with an
argument.
Measured against the real exported
invokesScriptonorigin/main(145ba75), before anyedit:
The card's warning about the third row holds exactly:
echo "::error::check-shard-attestation.mjs --verify exited 1"is green only because
::error::fuses onto the basename token. Write the same annotationwith a space and it counted. Both spellings are now pinned, because a fix that only handles
the obvious one leaves the fragile one behind.
Direction is why this was worth fixing:
invokesScriptfeeds presence questions -- does anattesting job end with the
--emitpair, does an aggregate gate run--verify-- and a presenceassertion fails toward a silent green. The symptom is the guard reporting that a leg attests
when it does not.
The packaging decision: shared, and the sibling now imports it
The two files already had exactly one import edge, and it points
check-required-contexts->check-shard-attestation(that file has importedshellCommandsfrom this one since #10884). That settles the question mechanically rather thanby taste: a helper kept in the importer 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.
commandWordsandinvokesnow live besideshellCommands-- commands out of arun:block, then words out of a command, one layer above the other in one home -- andcheck-required-contexts.mjsimports them over the edge that already existed.Rejected alternatives, with reasons:
at the time by a real obstacle -- the source module ran its gate at load time, so importing it
executed a second gate. That obstacle does not exist here:
check-shard-attestation.mjshasbeen behind
isEntrypoint()all along, which is why the sibling already imports from it.scripts/shell-words.mjs. A third home for a two-layer lexer whose lower layer isalready exported from, and imported out of, this file. Keeping the layers together would mean
moving
shellCommandstoo -- a larger blast radius for no gain.The asymmetry, preserved
The program word must be unquoted; arguments may be quoted freely. Refusing a quoted
--verifywould narrow a presence assertion into a false red, which is the failure mode thatgets a pin loosened rather than fixed. Pinned in both directions.
#6589 is not regressed
Every #6589 pin is untouched and green: the script in one command with the flag in another,
git rev-parse --verify,git show-ref --verifyon a continuation line, the leading- andtrailing-comment cases, and the
&&boundary. The bare-substring case that motivated adjacencystays false, and the two quoted-
#/ quoted-||cases stay true.The live verdict did NOT move
The card says this is latent, not currently mis-firing, and that is what the tree shows. The
real pass is byte-identical before and after:
Self-test, 92 -> 103 assertions (elisions marked; the rest is the tool's own line, verbatim):
Ablations -- predictions written down BEFORE each run
Both self-tests collect failures rather than aborting, so each run lists the full failing set
and a prediction can name all of it. Every mutation was proven on disk by grep counts of the
injected and the removed text -- never by an editing tool's exit code. (A first attempt with
perl -0piwas a silent no-op: exit 0, counts unchanged. It was caught by the count andredone.) Every restore leg was proven byte-identical with
git diff --quiet(exit 0).A -- drop the unquoted-PROGRAM condition only. Predicted, non-obviously, that the two halves
are independently load-bearing and that this is the narrower half: once a quoted region is one
word, the
echoblobs no longer resemble the basename at all, so only a quoted word that isexactly the program spelling needs this condition. Predicted exactly one failure in
check-shard-attestation and green in check-required-contexts.
Observed: exactly that.
B -- revert
commandWordsto the pre-#10889 tokenizer (the defect itself). Predicted eightnamed failures in check-shard-attestation and, critically, that check-required-contexts would
also redden -- the proof that the helper is genuinely shared and not a leftover local copy.
Predicted not failing: the fused
::error::pin (green even under the defect -- the accidentthe card warned about) and both asymmetry pins (the old tokenizer stripped quotes off arguments,
so a quoted argument still read as present).
Observed: 8 failures, exactly the eight named, with exactly those exclusions; and the sibling
reddened with exactly the two predicted pins:
C -- delete the shared import. Predicted loud, never silent. Observed
ReferenceError: invokes is not definedat check-required-contexts.mjs:763, exit 1.Gates -- union re-run against final HEAD b9dec72
Gate set derived from the real changeset by
node scripts/pm/dispatch-gates.mjswith nohand-made path list (it read the merge base itself: 2 paths vs merge-base 145ba75, three-dot
semantics). All nine matched families green, each quoted from the gate's own verdict line:
pnpm check:shard-attestation103 assertions (... + the #10889 quoting pins)./2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).pnpm check:required-contexts150 assertions (...)/6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491).pnpm check:entry-guardcheck:entry-guard: 130 scripts/ file(s) -- every entry guard goes through invoked-as.mjs; 88 export bindings, 78 of them inert on importpnpm check:parse-guardcheck:parse-guard: 129 scripts/ file(s) -- every TypeScript parse goes through ts-parse.mjs.pnpm check:cross-package-test-inputsAll 104 self-test cases passed./OK: 13 package(s) read outside themselves, all declared...node scripts/check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) (71 unique) are covered...node scripts/check-cross-package-test-inputs.mjsOK: 13 package(s) read outside themselves, all declared...node scripts/check-required-contexts.mjs6 required context name(s) pinned across 2 workflow(s)...node scripts/check-shard-attestation.mjs2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).Plus
pnpm check:nul-bytes--check-nul-bytes: OK (scanned 6300 text file(s) ... no raw ASCII control bytes).-- and a manual control-byte scan of both edited files (no matches).eslint --no-inline-configon both changed files: exit 0, no findings.Every exit code was captured before any pipe (
cmd > log 2>&1; ec=$?), never throughtail.The #10853 filter trap was exercised as a negative control --
pnpm --filter @objectstack/definitely-not-a-package testprintedNo projects matched the filtersandexited 0 -- and it could not have greened anything here: the verification path above uses no
--filterat all.No changeset
scripts/**-only, publishes nothing -- a pure bug fix (AGENTS.md:943).skip-changesetappliedadditively via
POST .../labels, then read back.Generated by Claude Code
Generated by Claude Code