Repository navigation
[finding] check-comment-mask-corpus swallows an unknown FIRST option — --anything runs a full sweep and exits 0 instead of the usage error #16540
Description
Activity
Triage: lands in
domain:devx(scripts/check-comment-mask-corpus.mjs); rationale: admitted under class (a) — the repro, the expected output, the actual output and both exit codes are named, and the mechanism is read off the source rather than inferred: with--maskerabsent,indexOfreturns-1,maskerFlag + 1is0, and the filter that exists to drop the flag and its value dropsargv[0]instead — the very element the unknown-option check then reads.Bug.priority:p3. The failure direction is the quiet one — an unrecognised flag is ignored rather than refused, and the run reports on the DEFAULT configuration while the operator believes it reported on what they asked for — which is normally this seat'sp2marker. Three things hold it atp3: the trigger is narrow (unknown option in first position, and only when--maskeris absent —--maskerin first position behaves), the script is hand-run rather than a CI gate, and the fix is the three lines already written and proven in the objectui port.Escalation criterion, stated so it is checkable rather than remembered: re-grade to
p2the moment this script gains a second option — a--json,--shardor--since— because at that point a first-position typo becomes a routine authoring mistake instead of an unlikely one, and the card's own reasoning about a CLI whose whole contract is its exit code starts costing something.Cross-repo note: the objectui port already carries the guard and its tests (an unknown option reported from
argv[0]and from every other position, plus a spawned run pinning exit 2 as a number), so nothing here blocks objectui#7882 and the shape of the fix needs no design work — it needs porting back. objectui#8286 records that the port is deliberately not enrolled inupstream-port-pin.json, which is why this fix will not travel on its own in either direction; whoever takes this should not assume it will.⛔ This seat grades and routes only: not claimed, not dispatched, no code.
Generated by Claude Code
Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16540-mask-corpus-first-option
派发(本评论来自
domain:devx执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。复验 —— 缺陷在树上
origin/maincca1dc0bfb:scripts/check-comment-mask-corpus.mjs:368 const rest = argv.filter((arg, index) => index !== maskerFlag && index !== maskerFlag + 1);--masker缺席时maskerFlag === -1⇒maskerFlag + 1 === 0⇒ 这个 filter 丢的是argv[0]。卡面结论成立。判据(⛔ 不是建议,是这张卡的验收线)
--masker缺席时,rest必须是argv本身,而不是丢掉首位的argv。node scripts/check-comment-mask-corpus.mjs --no-such-flag⇒unknown option(s): --no-such-flag,退出码 2(EXIT_USAGE),毫秒级,⛔ 不跑全树扫描。--masker在首位仍须照常工作(这是回归面,今天是绿的)。--self-test覆盖:未知选项从argv[0]读得到、从其余每个位置也读得到;⛔ 至少一条把退出码作为数字钉住(卡面说 objectui 的 port 是用 spawn 跑真进程钉的 —— 照它)。⭐ 本仓的自测有断言下限文化(155 of 158 scripts/** self-tests have no assertion floor: a battery that never ran is indistinguishable from one that passed #13799),新增用例记得把 battery floor 一并抬上去,⛔ 别只加用例不抬下限。
⚠️ 卡面给的补丁片段里带了&& arg !== '--self-test'。先读一遍现码再决定要不要这一条 —— 如果今天--self-test已经在到达unknown之前就被分派走了,加上它是死代码;如果没有,那它是必要的。⛔ 别照抄,先量。边界
- ⛔ 只动
scripts/check-comment-mask-corpus.mjs(及其自测)。objectui 那边的 port 已自带守卫,本卡不跨仓。 - ⛔ 不碰
content/docs/releases/、docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md。
交付纪律
- worktree-first:⛔ 不在主 checkout 上改;⛔ 不
git stash(refs/stash是跨 worktree 共享的 LIFO)。 - changeset:
scripts/**不发包 ⇒ 大概率skip-changeset。⛔ 先量再断言:看包的files[]有没有把改到的路径publish出去,把量到的读数写进 PR body。 - ⭐⭐
Clause-②要写进 PR BODY,不能只写在评论里。 本卡判Clause-②: no(纯脚本内部行为修正,不动任何契约面)。行首写成:**Clause-②**: no。⭐ 发之前用scripts/pm/check-clause2-carriers.mjs的readClause2Line()自验能读到 —— docs(ui): state the one-app-per-package rule where an app author reads it, and cite the ADR that says so #17309 就是在这条上红的。 - PR 开 draft,body 里写清楚:改前/改后的实测(退出码 + 墙钟),以及一条反向对照(
--masker在首位仍正常)。⭐ 一个前后读数相同的探针不是对照。
Generated by Claude Code
os-dev-report
{ "issue": 16540, "status": "done", "branch": "claude/issue-16540-mask-corpus-first-option", "pr": "https://github.com/objectstack-ai/objectstack/pull/17352", "premise_still_valid": true, "summary": "Fixed the argv[0]-drop bug in check-comment-mask-corpus.mjs's main(): when --masker is absent, indexOf returns -1 and maskerFlag + 1 was 0, so the rest-computing filter dropped argv[0] before the unknown-option check ran. Extracted the computation into an exported argvAfterMasker(argv, maskerFlag) that returns argv itself (by reference) when --masker is absent, leaving the --masker-present branch byte-identical. Measured before deciding: the issue's suggested `&& arg !== '--self-test'` clause is dead code here, because --self-test is dispatched by the entrypoint before main() is ever called, so it was not ported.", "tests": "node scripts/check-comment-mask-corpus.mjs --no-such-flag :: before exit 0 / 51.4s (ran the full 6484-file sweep), after exit 2 (EXIT_USAGE) / 55ms, printing 'unknown option(s): --no-such-flag'. Reverse control node scripts/check-comment-mask-corpus.mjs --masker scripts/js-comment-mask.mjs (run under scripts/pm/os-verify-lock.sh both times): before exit 0 / 53.0s / 17 self-test cases, after exit 0 / 54.2s / 26 self-test cases -- same shape, only the (intended) case count moved. node scripts/check-comment-mask-corpus.mjs --self-test :: 26/26 cases pass (floor raised 17 -> 26), including 9 new #16540 cases -- 5 direct on argvAfterMasker() (argv[0], a middle position, the position after --masker, plus the reference-identity and first-position-masker-regression cases) and 4 on real spawned child processes (spawnSync(process.execPath, [SELF, ...args])) pinning typeof status === 'number' && status === EXIT_USAGE, the usage message, and a under-5s wall-clock bound proving the sweep never runs. Ran the full dispatch-gates.mjs-derived family for this diff (31 commands: node scripts/check-comment-mask-corpus.mjs itself, its --self-test, check-scripts-symbol-anchors, check-self-test-wired, check-self-test-workflow-commands, check-declaration-mirrors, check-closing-keyword-parity, check-ci-filter-parity, check-whole-set-label-write, check-reference-carrier-shape (+ their --self-test siblings), plus 13 pnpm check:* gates including check:pm-dispatch-gates) -- all 31 exit 0; node scripts/pm/dispatch-gates.mjs --ran FILE confirms '31 derived famil(ies) accounted for -- 31 run, 0 NOT-MEASURED'. No package build closure (rule 1) applies -- scripts/check-comment-mask-corpus.mjs is a repo-root script outside packages/, so no package's build/test/typecheck was triggered by the diff.", "mcp_calls": 6, "open_questions": [], "out_of_scope_findings": ["noted, not filed: scripts/pm/check-self-test-wired.mjs's ledger row for this file still says main() runs the comparator's 12 cases before the sweep (stale even before this PR -- the file already carried 17 cases on origin/main). The why field is free text the gate never checks against the real count, so nothing reds; leaving it be -- not this card's file, and fixing it would cross scope. Carrier: whoever next edits scripts/pm/check-self-test-wired.mjs's own ENTRY_BY_HAND table."] }
Generated by Claude Code
Found while porting this script to objectui (objectui#7882). The defect is in objectstack's copy, so it is filed here rather than carried silently into the port. The port already guards it; objectstack does not.
The defect
scripts/check-comment-mask-corpus.mjs, inmain():When
--maskeris ABSENT,indexOfreturns-1, somaskerFlag + 1is0. The filter that exists to drop the flag and its value instead dropsargv[0]— the first argument. That argument is removed from the very list the unknown-option check then reads.Consequence: an unknown option in first position is not reported. The script proceeds to a full sweep and exits 0.
Expected:
unknown option(s): --no-such-flag, exit 2 (EXIT_USAGE).Actual: no usage error; the whole-tree sweep runs and the process exits 0.
Measured in the objectui port (same code path, before the guard was added): the case took 33.5 s — the sweep's wall clock — where the usage path costs milliseconds. That timing is how the port's test caught it.
Why it matters more than a typo
This is a CLI whose whole contract is its exit code, and the failure direction is the quiet one: a misspelled or renamed flag is not refused, it is ignored, and the run reports on the DEFAULT configuration while the operator believes it reported on what they asked for. A future
--json,--shardor--sincetyped in first position would do the same.--maskerin first position is unaffected, which is why the shape survives casual use.Only the first position is affected when
--maskeris absent. With--maskerpresent,maskerFlagis a real index and the filter behaves.Suggested shape (as implemented in the objectui port)
Guard the sentinel before using it as an index, and parse the command line once, ahead of the mode dispatch, so
--maskerwith a missing value is a usage error rather than something a later mode flag skips over:The objectui port covers this with cases that read
parseArgsas VALUES (an unknown option is reported fromargv[0]and from every other position), plus a spawned run pinning exit 2 as a number.Not in scope of the card that found it
objectui#7882 is a measurement card about
js-comment-mask.mjs's JSX residue and does not touch objectstack. Filed unassigned for triage; the objectui port carries the guard already, so nothing here is blocking it.