You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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 #10797
Found while implementing #10495 (PR #10796). Filing rather than folding in: that card's surface is the reachability report, and this is a separate failure of the same script's write ordering.
The shape
scripts/bump-objectui.sh writes the pin file before it reads anything else about the commit:
If the commit object is not present in the objectui checkout, git log fails, set -e kills the script — and .objectui-sha has already been rewritten. Measured on a throwaway objectui whose HEAD commit object was deleted (git 2.43.0):
The operator is left with a modified .objectui-sha in the working tree, no changeset, no commit, and a bare fatal: bad object as the whole explanation. Re-running does not self-correct: the pin file now holds the bad SHA, so the next run compares against it.
Why the default path can reach it
This is not only the explicit-argument case. Measured, git 2.43.0: git rev-parse HEAD exits 0 and prints the sha even when that commit object is missing from the object store — it resolves the ref without reading the object. So a plain bump-objectui.sh with no argument can carry an unreadable SHA all the way to the pin write. (A partial clone with the object not fetched, or an interrupted/corrupt object store, gets there.)
PR #10796 makes the reachability half of this loud — it reports "COULD NOT DETERMINE … the commit object is NOT PRESENT" before the write — so the run is no longer silent about it. It deliberately does not change the write ordering or the exit behaviour, which is what this card is for.
Likely shape of a fix
Read what the bump needs from the commit (at minimum SUBJECT_LINE) before the first mutation of .objectui-sha, so an unreadable commit fails with the script's own message and an untouched working tree. Whether an absent object should be a hard refusal rather than a warning is a judgement for triage; #10495's ruling (warning, not gate) was about a non-main pin, not about a pin nobody can read.
Triage: pm:queue + type Bug (scripts/bump-objectui.sh → devx by the lane table). On the judgement the card left to triage: reorder the reads before the first mutation of .objectui-sha, and make an unreadable commit object a hard refusal with an untouched working tree — that is restoring the invariant "a failed run leaves no half-applied state", not a product decision; #10495's warning-not-gate ruling was about a non-main pin and does not extend to a pin whose object cannot be read (there is nothing meaningful to pin). Hot-file note for the devx seat: same script as in-flight PR #10796 (#10495) — serialize this card after that PR lands; fold-or-serial answer: serial (different defect shape — write ordering vs reachability report — so the five-gate family test fails on gate ①).
⭐ Serial cleared: the triage comment's hot-file note pointed at in-flight PR #10796 on this same script — that PR merged at ~13:32Z. Branch from current origin/main and re-derive; the reachability-report work is already in the file.
Triage's ruling, carried verbatim: reorder the reads before the first mutation of .objectui-sha, and make an unreadable commit object a hard refusal with an untouched working tree. That restores the invariant "a failed run leaves no half-applied state" — it is not a product decision. ⚠️#10495's warning-not-gate ruling was about a non-main pin and does not extend to a pin whose object cannot be read (there is nothing meaningful to pin).
Standing constraints
⛔ Governed, human-merge-only: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md — propose wording in the report.
⛔ Never edit content/docs/releases/**. ⛔ Never git stash — refs/stash is shared by every worktree of the repo.
⚠️A pipe swallows the exit code. Capture as cmd > log 2>&1; ec=$?.
{
"issue": 10797,
"status": "done",
"branch": "claude/issue-10797-bump-objectui-write-ordering",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10866",
"premise_still_valid": true,
"summary": "Verified the defect at current origin/main (post-#10796): the pin write sat at line 313, the `git log -1 --format=%s` read at 317. Every read of the objectui commit is now hoisted ahead of the first mutation of `.objectui-sha`, and the guard IS that read rather than a `cat-file -e` proxy for it -- a present-but-unreadable object passes presence and still kills `git log`. An unreadable commit object is a hard refusal (exit 1) with the tree untouched, per triage's ruling; the #10495 warning-not-gate path for a not-on-main pin is unchanged. `report_objectui_reachability`'s Q2 presence probe was removed because the preflight made it a second, weaker implementation of one rule (the file's own stated discipline); its rc!=1 branch remains the backstop and was observed firing during the ablation. FIXTURE TRIAGE: `objectui-changeset-digest.mjs`'s `#10495 R5c` pinned exactly the branch removed ('an absent object is reported as UNDETERMINED') and went red -- rewritten, not deleted, into R5c/R5d/R5e demanding the refusal, the byte-identical pin file and the named remedy; R5a/R5b untouched since both git behaviours they measure are what make the case reachable. `docs/releases-maintenance.md` claimed an absent object lands in the 'cannot be answered' bullet, which my change falsifies -- bullet corrected, new section added. OBSERVATION (recorded, not filed): before this PR touched the digest, `dispatch-gates` scored `check:objectui-changeset` *silent* for a diff editing only `bump-objectui.sh`, though that gate's self-test drives that exact file -- not a CI hole (lint.yml declares no pull_request path filter) and dispatch-gates documents `silent` as its weakest claim. No changeset: nothing here is published (maintainer shell script, its self-test, a scripts/ node fixture, internal docs, root private package.json, a lint step) -- `skip-changeset` applied additively via POST /issues/10866/labels and read back as ['documentation','ci/cd','dependencies','size/m','skip-changeset'], so the concurrent labeler bot writes were preserved.",
"tests": "Union re-run at final head 0f90719bbd (`git rev-parse --short HEAD` from that run), 0 failures; every exit code captured as `cmd > log 2>&1; ec=$?`, never after a pipe. NEW SELF-TEST `scripts/bump-objectui.selftest.sh` (`pnpm check:objectui-bump`, wired into the lint job as an unconditional step; offline, no node, no network): verdict line `✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.` ABLATION, both legs, no build/dist involved (the self-test copies the script's real bytes into a throwaway FRAMEWORK_ROOT at run time, so there is no artifact to rebuild): MUTATION leg -- preflight block deleted and the two reads put back after the write; confirmed ON DISK by anchored marker counts, not by the editor's exit code: `if ! SUBJECT_LINE=` 1->0, `REFUSING to bump` 1->0, `^SUBJECT_LINE=\"$(git` 0->1, byte count 29809->26372, and `grep -n` showing write at 321 before read at 325. Result RED, reproducing the card's measurement verbatim: `✓ refused (exit 128)` / `✗ .objectui-sha CHANGED — half-applied state: 3f56dec16e32…` / `✗ .objectui-sha was CREATED holding ee38fc640ec2…` / `✗ bump-objectui self-test FAILED — 4 assertion(s) failed, 9 passed.` Predicted direction was RED-on-byte-identity specifically, and that is what was observed -- note `refused (exit 128)` PASSED on the broken script, which is why the discriminating assertion is on the file's bytes (`cmp`) and not on the exit code or the message. RESTORE leg -- `git checkout <branch> -- scripts/bump-objectui.sh`, confirmed on disk (markers back to 1/1/0, byte count back to 29809, `git status --short` empty), self-test re-run GREEN (13/13). GATE VERDICT LINES, each quoted from the gate's own output: check:objectui-bump `✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.`; check:objectui-changeset `✓ objectui-changeset-digest --self-test: all checks passed` + `✓ objectui-range --self-test: all checks passed` (was EXIT=1 before the R5c triage, quoting my own refusal text -- caught locally); check:doc-authoring `✓ doc authoring guard: 389 files clean — no bare metadata literals.`; check:entry-guard `✓ check:entry-guard: 129 scripts/ file(s) — every entry guard goes through invoked-as.mjs`; check:parse-guard `✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.`; check:nul-bytes `✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)`; check:cross-package-test-inputs `All 90 self-test cases passed.`; check:type-check-coverage `check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger`; 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.` Also green: check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, and raw-node check-aggregator-roster / check-ci-filter-parity / check-cross-package-test-inputs / check-required-contexts / check-shard-attestation. check:entry-guard and check:parse-guard run by hand as briefed (dispatch-gates confirms parse-guard is 'unreachable by construction'). ZERO-MATCH TRAP CHECKED: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` verified to have really matched (`@objectstack/lint@17.1.0` and the script name echoed; grep for 'No projects matched' negative) -- its first run was EXIT=1 with ERR_MODULE_NOT_FOUND on `@objectstack/formula/dist/index.mjs`, i.e. an unbuilt dependency closure, not my change; after `pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build` it reports `✓ check:doc-formula-expressions self-test: 30 cases passed` + `✓ … 22 record-scoped formula example(s) across 416 files / 1443 TS blocks judged clean`. check:type-check-debt likewise first refused with its own #6376 unbuilt-closure guard (not a failure of this change); the full workspace closure was built and it then re-measured clean, tree still `git status --short` empty (no ledger rewrite). All heavy work went through `scripts/pm/os-verify-lock.sh -c` -- three acquisitions, verdicts `command-exit 0 · held the lock 133s`, `held 2s`, `held 277s (4m37s)`. Gate set re-derived with `node scripts/pm/dispatch-gates.mjs` (no paths) AFTER the second commit, which added `check:objectui-changeset` to the matched list. Control-byte self-scan over all five touched files: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` exit 1 (no hits).",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
huangyiirene commentedon Aug 21, 2026
Triage:
pm:queue+ type Bug (scripts/bump-objectui.sh→ devx by the lane table). On the judgement the card left to triage: reorder the reads before the first mutation of.objectui-sha, and make an unreadable commit object a hard refusal with an untouched working tree — that is restoring the invariant "a failed run leaves no half-applied state", not a product decision; #10495's warning-not-gate ruling was about a non-main pin and does not extend to a pin whose object cannot be read (there is nothing meaningful to pin). Hot-file note for the devx seat: same script as in-flight PR #10796 (#10495) — serialize this card after that PR lands; fold-or-serial answer: serial (different defect shape — write ordering vs reachability report — so the five-gate family test fails on gate ①).Generated by Claude Code
claude commentedon Aug 21, 2026
Claim:
/pm-dispatchdomain:devx execution seat (bug-first round).Session:
session_01DdCnBGcHeufjrq7drTD3wt· Branch:claude/issue-10797-bump-objectui-write-ordering· Worktree:objectstack-issue-10797Container & model:
S-M,mode:subagent,model: opusFile surface:
scripts/bump-objectui.sh(+ its self-test).⭐ Serial cleared: the triage comment's hot-file note pointed at in-flight PR #10796 on this same script — that PR merged at ~13:32Z. Branch from current
origin/mainand re-derive; the reachability-report work is already in the file.Triage's ruling, carried verbatim: reorder the reads before the first mutation of⚠️ #10495's warning-not-gate ruling was about a non-main pin and does not extend to a pin whose object cannot be read (there is nothing meaningful to pin).
.objectui-sha, and make an unreadable commit object a hard refusal with an untouched working tree. That restores the invariant "a failed run leaves no half-applied state" — it is not a product decision.Standing constraints
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md— propose wording in the report.content/docs/releases/**. ⛔ Nevergit stash—refs/stashis shared by every worktree of the repo.cmd > log 2>&1; ec=$?.pnpm --filter <typo>exits 0 with "No projects matched the filters" — a whole verification round can be fictitious and read as green #10853):pnpm --filter <name>with a name that does not exist printsNo projects matched the filtersand EXITS 0. A whole verification round can be fictitious and read as green. Verify your filter actually matched before trusting any run. (My own briefing carried a wrong package name this morning and a dev caught it.)POST /issues/N/labels), never a whole-set PUT; read them back after the size labeler runs.Report back to me; I review against GitHub and land it.
Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
{ "issue": 10797, "status": "done", "branch": "claude/issue-10797-bump-objectui-write-ordering", "pr": "https://github.com/objectstack-ai/objectstack/pull/10866", "premise_still_valid": true, "summary": "Verified the defect at current origin/main (post-#10796): the pin write sat at line 313, the `git log -1 --format=%s` read at 317. Every read of the objectui commit is now hoisted ahead of the first mutation of `.objectui-sha`, and the guard IS that read rather than a `cat-file -e` proxy for it -- a present-but-unreadable object passes presence and still kills `git log`. An unreadable commit object is a hard refusal (exit 1) with the tree untouched, per triage's ruling; the #10495 warning-not-gate path for a not-on-main pin is unchanged. `report_objectui_reachability`'s Q2 presence probe was removed because the preflight made it a second, weaker implementation of one rule (the file's own stated discipline); its rc!=1 branch remains the backstop and was observed firing during the ablation. FIXTURE TRIAGE: `objectui-changeset-digest.mjs`'s `#10495 R5c` pinned exactly the branch removed ('an absent object is reported as UNDETERMINED') and went red -- rewritten, not deleted, into R5c/R5d/R5e demanding the refusal, the byte-identical pin file and the named remedy; R5a/R5b untouched since both git behaviours they measure are what make the case reachable. `docs/releases-maintenance.md` claimed an absent object lands in the 'cannot be answered' bullet, which my change falsifies -- bullet corrected, new section added. OBSERVATION (recorded, not filed): before this PR touched the digest, `dispatch-gates` scored `check:objectui-changeset` *silent* for a diff editing only `bump-objectui.sh`, though that gate's self-test drives that exact file -- not a CI hole (lint.yml declares no pull_request path filter) and dispatch-gates documents `silent` as its weakest claim. No changeset: nothing here is published (maintainer shell script, its self-test, a scripts/ node fixture, internal docs, root private package.json, a lint step) -- `skip-changeset` applied additively via POST /issues/10866/labels and read back as ['documentation','ci/cd','dependencies','size/m','skip-changeset'], so the concurrent labeler bot writes were preserved.", "tests": "Union re-run at final head 0f90719bbd (`git rev-parse --short HEAD` from that run), 0 failures; every exit code captured as `cmd > log 2>&1; ec=$?`, never after a pipe. NEW SELF-TEST `scripts/bump-objectui.selftest.sh` (`pnpm check:objectui-bump`, wired into the lint job as an unconditional step; offline, no node, no network): verdict line `✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.` ABLATION, both legs, no build/dist involved (the self-test copies the script's real bytes into a throwaway FRAMEWORK_ROOT at run time, so there is no artifact to rebuild): MUTATION leg -- preflight block deleted and the two reads put back after the write; confirmed ON DISK by anchored marker counts, not by the editor's exit code: `if ! SUBJECT_LINE=` 1->0, `REFUSING to bump` 1->0, `^SUBJECT_LINE=\"$(git` 0->1, byte count 29809->26372, and `grep -n` showing write at 321 before read at 325. Result RED, reproducing the card's measurement verbatim: `✓ refused (exit 128)` / `✗ .objectui-sha CHANGED — half-applied state: 3f56dec16e32…` / `✗ .objectui-sha was CREATED holding ee38fc640ec2…` / `✗ bump-objectui self-test FAILED — 4 assertion(s) failed, 9 passed.` Predicted direction was RED-on-byte-identity specifically, and that is what was observed -- note `refused (exit 128)` PASSED on the broken script, which is why the discriminating assertion is on the file's bytes (`cmp`) and not on the exit code or the message. RESTORE leg -- `git checkout <branch> -- scripts/bump-objectui.sh`, confirmed on disk (markers back to 1/1/0, byte count back to 29809, `git status --short` empty), self-test re-run GREEN (13/13). GATE VERDICT LINES, each quoted from the gate's own output: check:objectui-bump `✓ bump-objectui self-test PASSED — 13 assertions across 4 cases.`; check:objectui-changeset `✓ objectui-changeset-digest --self-test: all checks passed` + `✓ objectui-range --self-test: all checks passed` (was EXIT=1 before the R5c triage, quoting my own refusal text -- caught locally); check:doc-authoring `✓ doc authoring guard: 389 files clean — no bare metadata literals.`; check:entry-guard `✓ check:entry-guard: 129 scripts/ file(s) — every entry guard goes through invoked-as.mjs`; check:parse-guard `✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.`; check:nul-bytes `✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)`; check:cross-package-test-inputs `All 90 self-test cases passed.`; check:type-check-coverage `check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger`; 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.` Also green: check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, and raw-node check-aggregator-roster / check-ci-filter-parity / check-cross-package-test-inputs / check-required-contexts / check-shard-attestation. check:entry-guard and check:parse-guard run by hand as briefed (dispatch-gates confirms parse-guard is 'unreachable by construction'). ZERO-MATCH TRAP CHECKED: `pnpm --filter @objectstack/lint run check:doc-formula-expressions` verified to have really matched (`@objectstack/lint@17.1.0` and the script name echoed; grep for 'No projects matched' negative) -- its first run was EXIT=1 with ERR_MODULE_NOT_FOUND on `@objectstack/formula/dist/index.mjs`, i.e. an unbuilt dependency closure, not my change; after `pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build` it reports `✓ check:doc-formula-expressions self-test: 30 cases passed` + `✓ … 22 record-scoped formula example(s) across 416 files / 1443 TS blocks judged clean`. check:type-check-debt likewise first refused with its own #6376 unbuilt-closure guard (not a failure of this change); the full workspace closure was built and it then re-measured clean, tree still `git status --short` empty (no ledger rewrite). All heavy work went through `scripts/pm/os-verify-lock.sh -c` -- three acquisitions, verdicts `command-exit 0 · held the lock 133s`, `held 2s`, `held 277s (4m37s)`. Gate set re-derived with `node scripts/pm/dispatch-gates.mjs` (no paths) AFTER the second commit, which added `check:objectui-changeset` to the matched list. Control-byte self-scan over all five touched files: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` exit 1 (no hits).", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
fix(devx): deepen on an absent pin endpoint, and refuse an unwalkable…
fix(devx): refuse instead of degrading when a walkable range's change…
docs(devx): correct the pin-write comment for the reordered mutation