Skip to content

fix(pm): check-half-states takes the checkout that SERVES the board, and refuses a foreign one - #19288

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-19191-check-half-states-cwd
Sep 20, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-19191-check-half-states-cwd

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #19191

Clause-②: no

scripts/pm/check-half-states.mjs took three readings off the LOCAL checkout with plain
execFileSync git calls and no cwd, so each inherited process.cwd() while the board it reports on
is named by PM_SWEEP_REPO. The filer measured both directions on the same board with the same script
(#19191, comment 5740947564): with cwd in objectstack, H17's trigger-file index validated objectui's
candidate paths against objectstack's 8888 tracked files (objectui had 7945) and H57 read
NOTHING for the whole run — and the row it never read was hiding a scheduled lane dead through five
consecutive fires (objectstack-ai/objectui#10010). Internally consistent, externally wrong.

维护者速读(草稿)

改了什么 —— 给这个巡检工具加了一个环境变量 PM_SWEEP_CHECKOUT,用来回答「哪个本地检出服务于这块
board」。设了它,三处本地 git 读取(H17 的 tracked-file 判据、H57 的 workflow 文件、origin)就都在那棵
树上读;那棵树的 origin 不是被扫的仓时,整轮巡检响亮拒绝(退出码 3),⛔ 绝不静默换一棵树读。不设它,
行为与今天逐字一致 —— 只多了一句:H17 页脚现在写明它到底在哪棵树上读的(路径 + origin)。

为什么改 —— 这不是「跑错目录」的操作失误,而是一个没有症状的读数缺陷。跨仓巡检是常态(工具住在
objectstack,board 可以是 objectui),而 H17 的页脚正是在告诉派发席位「派发前请拿你的文件面和这张清单求
交集」。清单是拿另一个仓的文件列表验过的,只在另一个仓存在的触发文件被静默丢弃 —— 席位读到的是一份干净
清单,而那份干净是假的。H57 那半反而是诚实的:它明说自己什么都没读,代价是那一轮真有红行没人看见 ——
本 PR 的实测里那是两行,其中一行连卡都没提到。

风险与代价(含回滚) —— 风险面很窄:变量不设时是逐字旧行为(自测里有一条专门盯这件事),巡检
workflow 本身不设它,所以线上那条 lane 的行为不变。新增的唯一失败模式是「变量设错路径」,而那正好是本卡
要的那个响亮拒绝。回滚 = revert 这两个 commit,没有数据迁移、没有已发布面(scripts/pm/** 不随任何包
发布,故无 changeset)。

席位意见 ——

你要做的 —— 无需维护者动手。若希望巡检 workflow 把这棵树写明(目前不需要,因为 runner 的检出就是被扫
的仓),那是 .github/workflows/half-state-patrol.yml 的一行 env,已写在下面的 Acceptance notes 里,留给
单独一个由人合的改动 —— 本 PR ⛔ 不碰 workflow(#19259#19225 正在改它)。

What changed, mechanically

  • PM_SWEEP_CHECKOUT — one knob, beside PM_SWEEP_REPO and PM_SWEEP_CLOSED_FLOOR in --help,
    carrying the PATH of the checkout that serves the board. resolveSweepCheckout(env) resolves and
    trims it; whitespace is unset.
  • One git read site. All three readers (readTrackedFiles, readRepoRoot, readOriginUrl) now go
    through a single gitRead(args, extra) helper that passes that path as cwd. That is the only
    execFileSync git call left in the file, and the self-test pins the count at one — so a fourth
    reader cannot be added later without the cwd. That is the card's mechanism assumption turned into a
    measured property instead of a belief.
  • The refusal. checkoutPrerequisite(sweepRepo, env, originUrl) is a pure verdict in the shape
    reportPrerequisiteNotMet prints: null when the knob is unset (today's behaviour is not a
    prerequisite) or when the named tree really serves the board, otherwise the file's own exit-3
    PREREQUISITE NOT MET, named. It is answered FIRST in sweep() — ahead of the transport probe — so a
    foreign checkout costs zero requests and reads nothing.
  • localCheckoutServes resolves the same way: the knob leg first (it names the tree, so the
    origin read IN that tree is the authority), then GITHUB_REPOSITORY (which names the RUNNER's
    tree, i.e. exactly not the knob's), then the checkout's origin. H57 therefore judges when the knob
    names the right tree, instead of refusing for the whole run.
  • The H17 footer names the tree it read — path plus origin — beside its oracle size, in BOTH
    oracle states (read, and EMPTY BY FAILURE), knob set or not. A wrong-tree reading is internally
    consistent; the tree it names is the only thing that tells it from a right one.

Readings

reading before after
scripts/pm/check-half-states.mjs lines 36,176 36,256 — net +80, budget +80
--self-test cases 5,063 5,081 (+18), exit 0
battery roster 6 batteries, each above its pin unchanged, each above its pin

Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at this
branch → 39 runnable commands, all run locally with the exit code captured BEFORE any pipe, all 0. The
per-command verdicts are in this card's os-dev-report comment. Control-character self-scan over the
changed file: no match. No package contains scripts/pm/**, so no dependency-closure build and no
package test suite is owed; repo-wide scans stay CI's.

The firing pair, live

Both legs ran at this branch's head against the objectui board, from the objectstack tool.

Knob = the tree that serves the boardPM_SWEEP_REPO=objectstack-ai/objectui with
PM_SWEEP_CHECKOUT=/home/user/objectui, detached under the shared heavy-verify lock (held 398s, waited
0s — shared-box seconds, not idle-box figures): exit 0, 502 half-states over 374 open
pm-/p0-labeled issues. The H17 footer:

(read on 75 of 75 open `pm:on-hold` card(s); 8142 tracked file(s) in the oracle.) Read in `/home/user/objectui` (`origin` `https://github.com/objectstack-ai/objectui`).

8142 is objectui's LIVE tracked count today (git -C /home/user/objectui ls-files | wc -l = 8142); the
filer read 7945 on 2026-09-19 and the board has moved since. This worktree reads 9031, so the two trees
are still 889 apart — the number that never used to appear is now the one printed, with the tree it came
from beside it.

H57 is judged rather than UNRESOLVED: 10 workflow(s) on the swept repo declare a schedule; 8 were judged against their latest event=schedule run and 0 are UNJUDGED rather than clean because that read failed. It filed two red rows, neither reachable by any wrong-cwd sweep:

Knob = a foreign tree — same board, PM_SWEEP_CHECKOUT=/home/user/objectstack: exit 3, nothing
swept, nothing spent:

check-half-states: PREREQUISITE NOT MET — PM_SWEEP_CHECKOUT="/home/user/objectstack" does not serve `objectstack-ai/objectui`

  this checkout's `origin` is `objectstack-ai/objectstack` while the sweep reads `objectstack-ai/objectui`.

  H17 validates every on-hold trigger path against that checkout and H57 classifies its workflow
  files there, so a foreign tree renders an index whose paths were checked against another repo.

  Fix:  point PM_SWEEP_CHECKOUT at the checkout whose `origin` IS the swept board,
        or unset it and run the sweep from inside that checkout.

Reverse verification — both new pins can fail

Each ran from the COMMITTED state, mutated through scripts/ablation-replace.mjs (which proves the
write landed on disk and restores byte-exactly), and each turned exactly ONE case red out of 5,081.
The committed blob is f49d566ed276.

ablation mutation landed case that went red restore
strip the cwd from the one git-read site anchor 1 to 0, blob f49d566ed276 to 994882b2fcfc #19191 knob: …and that one site takes the knob as its cwd — got false, want true blob back to f49d566ed276 == HEAD, git diff HEAD empty
re-spell readRepoRoot with its own execFileSync git call — a fourth unrouted reader anchor 1 to 0, blob f49d566ed276 to ecd24ab63593 #19191 knob: ONE git read site in the file, so a fourth reader cannot skip the cwd — got 2, want 1 same

The first is the one that mattered most: before that pin existed, stripping the cwd left the whole
5,080-case suite green, i.e. the fix's central wiring had no test that could fail. The second is what
makes the card's "the three readers are the ONLY local-tree reads" a property the suite enforces.

Acceptance notes


Generated by Claude Code

…and refuses a foreign one

The three local git reads — `ls-files` (H17's tracked-file oracle), `rev-parse
--show-toplevel` and `remote get-url origin` (H57's workflow files) — ran with no
`cwd`, so each inherited whatever tree the shell stood in while `PM_SWEEP_REPO`
named another board. Measured 2026-09-19: a sweep of objectui validated its H17
trigger-file index against objectstack's 8888 tracked files (objectui has 7945)
and H57 refused for the whole run, hiding a scheduled lane dead through five
consecutive fires.

`PM_SWEEP_CHECKOUT` now names the tree. Set, the three reads take it as `cwd`
through one `gitRead` helper — the only `execFileSync('git'` site in the file, so
a fourth reader cannot be added without the cwd — and a checkout whose `origin`
is not the swept board refuses with the file's exit-3 PREREQUISITE NOT MET rather
than reading a second repo. `localCheckoutServes` resolves the knob first, so H57
judges when the knob names the right tree. Unset, behaviour is unchanged — plus
the H17 footer now names the tree it read (path + `origin`) beside its oracle
size, because a wrong-tree reading is internally consistent and the tree is the
only thing that distinguishes it from a right one.

Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2
Co-authored-by: Claude <noreply@anthropic.com>
… `cwd`

The count pin proves there is exactly ONE `execFileSync` git site in the file; it
says nothing about that site passing a `cwd`. Stripping the `cwd` left the whole
suite green, i.e. the fix's central wiring had no failing test. This pins it on
the source, assembled so the case cannot satisfy itself out of its own text.

Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6b1ff2cdc4f507e880d686a542f718b3831e5e6c

In-seat review by the domain:skills seat at the contract-review tier (served tier read at seating, #7623 marker 5738863635) — the lane's record for a scripts/pm/** landing (Tier S: the seat lands through the queue on this record + --pair 0 + every check green; the register lists no scripts/pm/** row — check-governed-merges --pr 19288: 「NOT governed — ordinary queue landing」 — so the record is the lane's own discipline). Clause-②: no on the claim (5748809579) and at line start in the PR body. Verified against GitHub (GET /pulls/19288/files: one file, scripts/pm/check-half-states.mjs +93 / −13; two commits ed059414 · 6b1ff2cd, trailer pairs model-free) and the diff read in full, ⛔ not against the report (5749138366).

① Derived judgments

② Semver level

None — scripts/pm/** publishes nothing; skip-changeset applied by the seat (precedent PRs #19173 / #19280) so this repo's Check Changeset exempts.

③ Boundary flags

Implemented-by: claude/issue-19191-check-half-states-cwd
Reviewed-by: session_01W5y9kRg1YtYaMQYExVLRc2

VERDICT: PASS


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 20, 2026 10:12
@os-tesla
os-tesla enabled auto-merge September 20, 2026 10:12
@os-tesla
os-tesla added this pull request to the merge queue Sep 20, 2026
Merged via the queue into main with commit 801415a Sep 20, 2026
38 of 39 checks passed
@os-tesla
os-tesla deleted the claude/issue-19191-check-half-states-cwd branch September 20, 2026 10:38
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