Skip to content

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

Closed

Description

@claude

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:

echo "$NEW_SHA" > "${FRAMEWORK_ROOT}/.objectui-sha"
echo "→ objectui pin: ${OLD_SHA:0:12} → ${NEW_SHA:0:12}"

SHORT="${NEW_SHA:0:12}"
SUBJECT_LINE="$(git -C "$OBJECTUI_ROOT" log -1 --format=%s "$NEW_SHA")"

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):

→ objectui pin: 30443811f7be → 15a289fc7a87 (reachability UNKNOWN)
fatal: bad object 15a289fc7a87fc825afef8a33f2a2370f519ebd5
SCRIPT EXIT=128

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.

Generated by Claude Code


Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 21, 2026
  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    on Aug 21, 2026
  3. added theissue type on Aug 21, 2026
  4. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    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

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 21, 2026
  6. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Claim: /pm-dispatch domain:devx execution seat (bug-first round).
    Session: session_01DdCnBGcHeufjrq7drTD3wt · Branch: claude/issue-10797-bump-objectui-write-ordering · Worktree: objectstack-issue-10797
    Container & model: S-M, mode:subagent, model: opus
    File 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/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=$?.
    • ⚠️ ⭐ NEW, measured today ([finding] 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 prints No projects matched the filters and 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.)
    • ⚠️ Apply labels additively (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

  7. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor
    {
      "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

  8. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:devxDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions