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 theissue type on Aug 21, 2026
  2. 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

  3. 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

  4. 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

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions