Skip to content

fix(tooling): read the objectui commit before the pin write, and refuse an unreadable one - #10866

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-10797-bump-objectui-write-ordering
Aug 21, 2026
Merged

fix(tooling): read the objectui commit before the pin write, and refuse an unreadable one#10866
os-zhuang merged 2 commits into
mainfrom
claude/issue-10797-bump-objectui-write-ordering

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#10797

The defect

scripts/bump-objectui.sh wrote the pin file before it read anything else about
the commit:

echo"$NEW_SHA">"${FRAMEWORK_ROOT}/.objectui-sha"# ← mutation
…
SUBJECT_LINE="$(git -C "$OBJECTUI_ROOT" log -1 --format=%s "$NEW_SHA")"# ← read

When the commit object could not be read, git log failed, set -e killed the run,
and .objectui-sha had already been rewritten: no changeset, no commit, a bare
fatal: bad object as the entire explanation, and a modified working tree the
operator had to clean up by hand. Re-running did not self-correct — the pin file now
held the bad SHA, so the next run compared against it.

Reachable with no argument at all: measured, git 2.43.0, git rev-parse HEAD exits
0 and prints the sha even when that commit's object is missing from the store,
because it resolves the ref without reading the object.

The fix

Every read of the objectui commit now happens ahead of the first mutation, and
the guard is that read rather than a probe standing in for it. cat-file -e
answers "is the object present", which is one failure short of the question that
matters — a present-but-unreadable object (corrupt zlib, truncated pack) passes it
and still kills git log. Doing the real read means anything that would fail later
has already failed with the tree untouched.

An unreadable commit object is a hard refusal, per the triage ruling on the
judgement the card left open: there is nothing meaningful to pin, and neither the
changeset entry nor the commit message can be derived from it.

The reachability report's warning-not-gate ruling is untouched and does not
extend here — it is about a pin that is not on main, a real commit you can still
pin, where origin/main may just be stale and the judgement is the operator's.
Its #10495 case R5c is retriaged rather than deleted (see below); nothing else
about that report changes, and its "not on origin/main" behaviour is unchanged.

report_objectui_reachability's presence probe (Q2) is removed: it became a second
implementation of a question the preflight now answers with a stronger test, and
this file's own standing rule is one implementation per rule. Its rc != 1 branch
remains the backstop and still reports 128 as the error it is, never as a verdict.

Evidence

New self-test scripts/bump-objectui.selftest.sh (pnpm check:objectui-bump, wired
into the lint job as an unconditional step). It drives the real bytes of the script
over throwaway git repos — offline, no node, no network, ~1s.

The assertion is on bytes, not on the message. Both the broken and the fixed
script exit non-zero on this input — 128 from set -e versus 1 from the refusal —
so "did it fail?" does not tell them apart, and a test asking only that would have
passed against the defect. What discriminates is the file.

Ablation, both directions. With the reordering reverted on disk (preflight block
removed, the two reads put back after the write — confirmed by marker counts
1→0 / 0→1 and the file's byte count 29809→26372), the self-test goes red on exactly
that assertion and reproduces the card's measurement verbatim:

 • unreadable commit object ⇒ refuses, .objectui-sha byte-identical
✓ refused (exit 128)
✗ .objectui-sha CHANGED — half-applied state: 3f56dec16e3220d508746dcb124a45f8ef4e547a
✗ no 'REFUSING to bump' in the output
• unreadable commit object + no existing pin file ⇒ the file is not created
✗ .objectui-sha was CREATED holding ee38fc640ec20e17ec7f622cc5b5aa9ce3c1e5a0
✗ bump-objectui self-test FAILED — 4 assertion(s) failed, 9 passed.

Restored (byte count back to 29809, markers back, tree clean against the commit):

✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.

Two of the four cases drive readable commits all the way through and assert the
pin moves, because exit 1 at the top of the script would satisfy every refusal
case on its own.

Fixture triage

objectui-changeset-digest.mjs's #10495 R5c pinned the branch this change
removes — "an absent object is reported as UNDETERMINED". That case is rewritten,
not deleted
: it builds the same absent-object fixture and now demands the refusal,
the byte-identical pin file, and the named remedy. R5a and R5b are untouched; both
git behaviours they measure are what make the case reachable at all.

Note for the reviewer: before this PR touched objectui-changeset-digest.mjs,
dispatch-gates scored check:objectui-changesetsilent for a diff that edits
only bump-objectui.sh — even though that gate's self-test drives that exact file.
Not a CI hole (lint.yml declares no pull_request path filter, so it runs on every
PR, and it is what caught this locally), and dispatch-gates documents silent as
its weakest claim. Recorded, not filed.

Docs

docs/releases-maintenance.md said an absent commit object lands in the
"cannot be answered" bullet. That is now false, so the bullet is corrected and a
short section documents the refusal, the invariant, and the remedy.

Gates

Union re-run at head 0f90719bbd (the final commit), all green:

pnpm check:objectui-bump ✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.
pnpm check:objectui-changeset ✓ objectui-changeset-digest --self-test: all checks passed
✓ objectui-range --self-test: all checks passed
pnpm check:doc-authoring ✓ doc authoring guard: 389 files clean — no bare metadata literals.
pnpm check:entry-guard ✓ check:entry-guard: 129 scripts/ file(s) — every entry guard goes through invoked-as.mjs
pnpm check:parse-guard ✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:nul-bytes ✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)
pnpm check:cross-package-test-inputs All 90 self-test cases passed.
pnpm check:type-check-coverage check-type-check-coverage: OK — 64/77 workspace packages type-checked
pnpm check:type-check-debt check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured
in 274.6s, 1912 raw tsc error(s) total, none above its recorded number.

Plus check:node-version, check:required-contexts, check:shard-attestation,
check:workflow-status-functions, check:doc-formula-expressions (filter verified
to have matched @objectstack/lint, not a zero-match exit 0), and the raw-node
check-aggregator-roster / check-ci-filter-parity / check-cross-package-test-inputs
/ check-required-contexts / check-shard-attestation. check:type-check-debt
needed the workspace closure built first (its own #6376 guard refused on an
unbuilt one); it was built and the gate then re-measured clean.

check:entry-guard and check:parse-guard were run by hand — dispatch-gates
cannot reach either from a scripts/** path.

No changeset

Nothing here is published: a maintainer shell script, its self-test, a scripts/
node self-test fixture, internal maintainer docs, the root private package.json,
and a lint workflow step. Taking skip-changeset.

Generated by Claude Code


Generated by Claude Code

…se an unreadable one
bump-objectui.sh wrote .objectui-sha before it read the commit subject out of
the objectui checkout. When the commit object could not be read, git log failed,
set -e killed the run, and the pin file had already been rewritten: no changeset,
no commit, a bare 'fatal: bad object' as the whole explanation, and a modified
working tree. Re-running did not self-correct, because the pin file then held the
bad SHA.
Every read of the objectui commit now happens before the first mutation, and the
guard IS that read rather than a cat-file -e standing in for it. An unreadable
commit object is a hard refusal with the working tree untouched -- there is
nothing meaningful to pin. The reachability report's warning-not-gate ruling is
about a pin that is not on main and is untouched here.
scripts/bump-objectui.selftest.sh (pnpm check:objectui-bump, wired into the lint
job) pins the invariant on the file's BYTES: both the broken and the fixed script
exit non-zero on this input, so only the file tells them apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
… refusal
The digest self-test's #10495 R5c pinned the branch this change removes: an
unreadable commit object reported as UNDETERMINED. Triage ruled that verdict
belongs to a pin that is not on main -- a real commit you can still pin -- and
not to one whose object cannot be read, where there is nothing to pin at all.
Rewritten rather than deleted: the fixture still builds the same absent-object
shape, and now demands the refusal, the untouched pin file (byte-identical) and
the named remedy. R5a and R5b are untouched -- both git behaviours they measure
are what make the case reachable in the first place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file labels Aug 21, 2026
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 14:48
@os-zhuang
os-zhuang enabled auto-merge August 21, 2026 14:48
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit cfb8ae4Aug 21, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10797-bump-objectui-write-ordering branch August 21, 2026 14:56
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32494150733 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.70s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 20 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bump-objectui.sh leaves a half-applied bump when the objectui commit object is absent: .objectui-sha is rewritten, then the run dies on git log

2 participants

@os-zhuang@claude