Repository navigation
fix(pm): dispatch-gates refuses an absent path unless --repo asserts whose tree it is - #18409
Merged
os-elon-musk merged 3 commits intoSep 16, 2026
Merged
Conversation
…whose tree it is A path named on argv that is not in this tree has two readings — a surface of THIS repo that is not written yet, and a path belonging to ANOTHER repo — and a repo-relative path cannot tell them apart. The tool resolved that ambiguity silently toward the harmless reading: exit 0, the path filed under "apply once this card's changeset exists", the gate list derived from THIS repo's workflows. A seat read that exit code and wrote "it refuses objectui paths by design" into five dispatch texts. Unasserted, the ambiguity is now NOT MEASURED and ends the run at exit 3 (this tool's existing EXIT_PREREQUISITE_NOT_MET), with the absent paths listed and both resolving spellings printed verbatim. An assertion naming this checkout restores the previous derivation byte-for-byte; an assertion naming another repo keeps its own exit 2 and its own text, unreached by the new branch. The absent-path predicate is extracted so the banner's count and the refusal's list are one reading. Globs are not counted (a pattern is not an absent file) and --changed paths are not covered (a tree's own diff cannot be another repo's). Three end-to-end CLI cases plus the pure half in --self-test. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bz6hxDBqK62NP2W1LATvnt
…thetical path The pending-changeset probe in the self-test hands the CLI a changeset path that does not exist — the whole point of it — with no assertion, so the new absent-path refusal turned it and the case below it red. Measured, not predicted: the battery named both. Asserting the repo is the whole repair, and it is the migration every dispatcher of a not-yet-written path owes. Both cases now assert a shape in each branch, so the no-readable-remote path pins the refusal rather than passing over empty output. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bz6hxDBqK62NP2W1LATvnt
… that owes it Five more probes name the pending-changeset path — hypothetical by design — and the battery reddened all seven cases reading them. Rather than repeat the flag five times, runCliHypothetical spells it once beside runCli, so every probe of a not-yet-written path carries the assertion and any future one is written through the same helper. ⛔ Not by weakening the refusal for the modes these probes use: the runs are genuinely ambiguous and the assertion is the answer. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bz6hxDBqK62NP2W1LATvnt
This was referenced Sep 16, 2026
os-elon-musk
marked this pull request as ready for review
September 16, 2026 10:28
os-elon-musk
deleted the
claude/issue-18383-dispatch-gates-absent-path-refuses
branch
September 16, 2026 10:48
This was referenced Sep 16, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…epo so a not-yet-written path derives a tier (objectstack-ai#18463) Fixes objectstack-ai#18425 Clause-②: no ## 维护者速读(草稿) **改了什么**:两处 agent 指令行里的命令拼写,从 `node scripts/pm/dispatch-gates.mjs --tier PATHS` 改为 `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier PATHS`(占位符在文件里是尖括号拼写,本正文为避开 GitHub sanitizer 写作 PATHS)。只动这两行 —— checklist-test 侧是同一条 bullet 内的两行重新折行 —— 两个文件的行数不变:pm-dispatch SKILL.md 812 / 812,checklist-test SKILL.md 234 / 238。 **为什么改**:`dispatch-gates.mjs` 对树上不存在的路径,在没有 `--repo` 申报时以 exit 3(NOT MEASURED)拒绝 —— 这是 PR objectstack-ai#18409 的有意设计,不是本卡的缺陷。PM 认领一张文件面尚未写出的卡(新 reference、新脚本)时照抄这两行,得到 exit 3、拿不到档位。改后的拼写照抄即 exit 0(下方 A7 读数)。 **风险与代价(含回滚)**:纯文案,不发布任何东西,不改脚本。代价一处:pm-dispatch SKILL.md 该行 120 B 预算容不下 +34 B —— 命令 85 B + 「引当次 … 输出」15 B + 「⛔ 不凭记忆」16 B + bullet 前缀与两个空格 4 B = 恰 120 B,所以主语「Container & model 行的档位」与逗号被删;保留「档位」的最短写法是 125 B,超预算。派发裁决禁止动该文件其它任何一行,所以「别处删一行换一行」不在本 PR 的选项内。回滚 = revert 这一个 commit。 **席位意见**:(留空) **你要做的(一个动作)**:接受去掉主语的写法则给一个 authorized APPROVED;若要保留「档位」,请裁定允许在 SKILL.md 别处删一行来换这条 bullet 的第二行,本 PR 再补一轮。 ## What changed — each line before / after, in bytes Byte counts are of the file's spelling, where the placeholder is written in angle brackets (7 B); this body writes it `PATHS` because the GitHub body sanitizer eats angle-bracket fragments even inside backticks. `.claude/skills/pm-dispatch/SKILL.md` :477 — file 812 / 812 (ceiling), `MAX_LINE_BYTES` 120, no line added: - before, 120 B: 「- Container & model 行的档位引当次 `node scripts/pm/dispatch-gates.mjs --tier PATHS` 输出,⛔ 不凭记忆。」 - after, 120 B: 「- 引当次 `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier PATHS` 输出⛔ 不凭记忆」 `.claude/skills/checklist-test/SKILL.md` :98–:99 — file 234 / 238, the same bullet re-wrapped across its existing two lines, no line added, the floor / default / ceiling parenthetical untouched: - before :98, 88 B: 「 `node scripts/pm/dispatch-gates.mjs --tier PATHS` 的输出、⛔ 不凭记忆(floor」 - before :99, 90 B: 「 sonnet · default opus · ceiling fable);每个给:该项 JSON、RUNNER.md、dogfood 技」 - after :98, 100 B: 「 `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier PATHS` 的输出、」 - after :99, 113 B: 「 ⛔ 不凭记忆(floor sonnet · default opus · ceiling fable);每个给:该项 JSON、RUNNER.md、dogfood 技」 ## Judgment call on the SKILL.md line (for the reviewer) The dispatch named three load-bearing pieces — the exact command spelling with `--repo objectstack-ai/objectstack --tier PATHS`, 「引当次 … 输出」 (quote THIS run's output) and 「⛔ 不凭记忆」 — and a 120 B gate. Those three plus the bullet prefix and two separating spaces measure exactly 120 B, so nothing else fits on the line: the subject prefix 「Container & model 行的档位」 and the comma are gone. The dispatch's own shortest candidate (「- 档位引当次 `…` 输出,⛔ 不凭记忆。」) measures 130 B; the shortest wording that keeps 「档位」 at all (「- 档位引当次 `…` 输出⛔不凭记忆」) measures 125 B. The bullet still sits in the claim-comment list and `--tier` names the tier itself; the claim template line (:808, untouched) keeps 「档位,引当次 --tier 输出」 as the subject. Typography note: a CJK glyph directly before ⛔ (「输出⛔ 不凭记忆」) has zero precedent on `origin/main` SKILL.md; the exact phrase 「⛔ 不凭记忆」 is preserved so a grep for it still hits this line. ## Self-verification of the instruction (dispatch A7) The command was extracted verbatim from between the backticks of SKILL.md :477 (`sed`, not retyped), the placeholder substituted with `packages/this-repo-has-no-such-package/src/index.ts` (absent from the tree, `ls` confirms), and run from the worktree; exit codes captured before any pipe: - new spelling → **exit 0**; output: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s), derived here, not recalled. The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 - control, bare spelling extracted the same way from `origin/main` :477 → **exit 3** (NOT MEASURED); output names the two `--repo` readings. - the spelling extracted the same way from checklist-test :98 is byte-identical to the SKILL.md one. ## Gates Derived from the merge base with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (17 commands, merge base `e915c190`), each run in the foreground with the exit code redirected before any pipe, then reconciled with `--ran` at `7a7f3c22`: 「Run reconciliation — 17 derived, 17 run, 0 NOT-MEASURED, 0 UNRUN.」 (exit 0). | command | exit | | --- | --- | | `pnpm check:pm-skill-ratchet` (rerun on the committed head `7a7f3c22`) | 0 — 「pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom 0)」 · 「checklist-test/SKILL.md is 234 lines (ceiling 238; headroom 4)」 | | `pnpm check:pm-skill-id-lint` | 0 — 27 file(s) clean | | `pnpm check:skill-frame-sync` | 0 | | `pnpm check:pm-governed-prose` | 0 | | `pnpm check:pm-half-states` | 0 — 4356 self-test cases | | `pnpm check:nul-bytes` | 0 — 8733 text files, no raw control bytes | | `pnpm --filter @objectstack/lint run check:doc-formula-expressions` | 0 — first run exit 3 PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` unbuilt); built both under the verify lock (`VERDICT command-exit 0`, 171 s), rerun → 0 | | `pnpm check:agent-test-spelling` · `pnpm check:doc-authoring` · `pnpm check:driver-memory-census` · `pnpm check:pm-governed-merges` · `pnpm check:refd-timer-probe` · `pnpm check:watch-hint-literal` | 0 each | | `node scripts/check-closing-keyword-parity.mjs` (and `--self-test`) · `node scripts/check-comment-mask-corpus.mjs` · `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 each | No package is touched, so there is no ① dependency-closure build and no ② package test; the control-character self-scan (`grep -naP` over the two files) is clean. ## Changeset None; label `skip-changeset`. AGENTS.md Post-Task Checklist step 3: 「that label is for a diff that publishes nothing from any released package」. `.claude/**` is in no package's `files[]` and is os-dev's declared fast lane for exactly this reason; nothing released moves. ## Acceptance notes - Rules layer (`.claude/skills/**`): draft PR; the dev never flips ready, never arms auto-merge, never requests review. Landing waits for an authorized APPROVED and the owning seat. - `scripts/pm/dispatch-gates.mjs` is deliberately untouched — its exit-3 refusal is the design landed in PR objectstack-ai#18409, not the defect. - Out of scope, not edited (dispatch A3): pm-dispatch SKILL.md :637 (a review heuristic) and :808 (the claim template, `引当次 --tier 输出` — not a command spelling); `references/contract-review.md` :17; `references/lanes/skills.md` :20; `references/platform-readings.md` :391 「`dispatch-gates.mjs --tier PATH` 逐路径现推」 — a mechanism reference without `node scripts/pm/`, so not a verbatim-copyable command; noted, not filed. Bearer: whichever skills-seat PR next edits `platform-readings.md`. - The dispatch's suggested route (drop 「Container & model 」, keep 「档位引当次 … 输出,⛔ 不凭记忆」) was measured at 130 B and falsified; the arithmetic above is the replacement. --- _Generated by [Claude Code](https://claude.ai/code/session_01HPfcjvF23QBoBj7P47DDxs)_ Co-authored-by: Claude <noreply@anthropic.com>
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 free
to 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 #18383
A path named on argv that is not in this tree has two readings — a surface of THIS repo that is not written yet, and a path belonging to ANOTHER repo — and a repo-relative path cannot tell them apart. The tool resolved that ambiguity silently toward the harmless reading: exit 0, the absent path filed under "apply once this card's changeset exists", the gate list derived from THIS repo's workflows. A seat read that exit code and wrote "it refuses objectui paths by design" into five dispatch texts.
Unasserted, the ambiguity is now NOT MEASURED: the run ends at exit 3 (
EXIT_PREREQUISITE_NOT_MET, this tool's existing NOT MEASURED code) with the absent paths listed and both resolving spellings printed verbatim, before any family derivation runs.The three shapes, before and after
Measured on
does/not/exist.mjs, exit codes captured before any pipe. Before =origin/main7358c1c5b; after = this branch atab0864186.--repo--repo objectstack-ai/objectstack--repo objectstack-ai/objectuiThe card's literal Reading,
--commands skills/objectui/SKILL.md AGENTS.mdwith no--repo, went from exit 0 to exit 3 namingskills/objectui/SKILL.md.Controls that must NOT move, all measured at
ab0864186:--repo(--commands scripts/pm/dispatch-gates.mjs)--tier packages/spec/src/index.ts--commands 'packages/*/src/index.ts') — a pattern is not an absent file--changed— a tree's own diff cannot be another repo'sThe refusal text:
Shape of the change
absentDeclaredPaths()extracted frombannerLines, so the banner's count and the refusal's list are ONE reading and can never name different sets.absentPathVerdict()— the verdict, mirroringrepoAssertionVerdict's shape. Returnsokwhen there is nothing absent, or when an assertion is present at all: the wrong-repo refusal runs first and keeps its own exit 2 and its own text, so--repo otheris unreached by this branch.argvPaths.length > 0, so only paths NAMED on argv reach it.Applied uniformly across modes,
--tierincluded, and that is a deliberate call worth a reviewer's eye:--tierderives no family, but the wrong-repo refusal above it ALREADY ends a--tierrun over another repo's slug (measured onorigin/main:--tier does/not/exist.mjs --repo objectstack-ai/objectuiexits 2), so the repo an answer is about is already load-bearing there. Exempting--tierwould resolve the same ambiguity silently toward the harmless reading in the one mode a claim comment pastes from. Consequence, stated plainly:--tier PATHwhere PATH is not yet written now exits 3 until--repois added.The one in-tree caller this broke, and its repair
The self-test's pending-changeset probe hands the CLI a HYPOTHETICAL changeset path — the whole point of it — with no assertion. The new branch turned that case and the one below it red. The battery named both; this was measured, not predicted. Asserting the repo is the whole repair, and it is exactly the migration every dispatcher of a not-yet-written path owes. Both cases now assert a shape in EACH branch, so the no-readable-remote path pins the refusal rather than passing over empty output.
Caller census
git grep -n "dispatch-gates.mjs" -- scripts .github .claude= 284 hits across 108 files. Almost all are prose: other gate scripts' headers citing this file. Narrowed to real invocations:package.jsonwirescheck:pm-dispatch-gates=node scripts/pm/check-dispatch-gates.mjs --self-test && node scripts/pm/check-dispatch-gates.mjs, which reaches this tool only byspawnSync(..., '--self-test')..github/workflows/lint.yml's eight hits are that one step plus comments. The three.claude/hooks/*hits are allowlist fixtures naming--self-testand--repo objectstack-ai/objectstack. So no CI or hook invocation passes a possibly-absent path without--repo, and none is changed here..claude/skills/pm-dispatch/SKILL.md:478and.claude/skills/checklist-test/SKILL.md:98, both spelling--tier PATHSwith no--repo. A PM claiming a card for a not-yet-written file gets exit 3 there until the flag is added. ⛔ Not changed in this PR (file surface is this script only) — listed as a finding for the seat.scripts/git-merge-regen.mjs:119already spells--commands --repo objectstack-ai/objectstack; unaffected.Gates
Derived in-worktree;
--commands PATH --repo objectstack-ai/objectstackand the no-path re-derivation off the real change set produced identical 30-command lists, and the list was re-derived unchanged at the final head. All 30 run, exit codes captured before any pipe, then reconciled with--ran:All 30 exit 0 at
ab0864186, includingpnpm check:pm-dispatch-gates— this tool's own battery, run exactly aslint.ymlstep 「PM dispatch-gates self-test」 runs it (pnpm check:pm-dispatch-gates, repo root, no extra env), detached per the file header's foreground-cap rule with the exit code captured to disk:The patch round, stated plainly
The first pushed head
284855e7fwent red on CI at exactly this step, and it reproduced locally at that same commit:✗ dispatch-gates self-test: 7 of 1742 case(s) failed.All seven were the two blocks that drive the CLI over the pending-changeset probe path — hypothetical by design, passed with no assertion — so the new refusal fired inside them and the derivation they assert on never ran.Fixed on the CASE side, which is what the card's contract prescribes: those probes now assert the repo, spelled once in a
runCliHypotheticalhelper besiderunCliso every probe of a not-yet-written path carries it and the next one is written through the same door. ⛔ No case was skipped, disabled or loosened; ⛔ no exemption was carved for the self-test, for a fixture directory or for a.changeset/prefix; ⛔ the refusal itself was not weakened.The battery went 1742 → 1746 cases: the value-bearing block's inner cases sit behind
if (doc && vbRows.length >= 1), so while the outer CONTROL was red they never ran at all. Four more cases now execute — the diagnostics-increase direction, not a rewrite.Acceptance notes
--jsonneeded no new shape: a refusal prints nothing on stdout and exits non-zero, which is what--repo otheralready did (measured onorigin/main:--json ABSENT --repo objectstack-ai/objectui= exit 2, 0 bytes of stdout). The new refusal matches it.originunreadable the refusal says the assertion cannot be CHECKED either, rather than printing a slug it does not have — noted, not filed: an unverifiable identity plus an absent path is NOT MEASURED in both directions, which is the honest answer.Generated by Claude Code
Generated by Claude Code