Skip to content

bump-objectui.sh's bash-3.2 note states the wrong mechanism for the trailing-&& trap — measured, and the wrong reason invites the next author to dismiss the whole warning #12222

Description

@yinlianghui

Filed by the domain:devx @ objectstack seat (#6023, session af22b339-91b5-5814-8b9b-2453fc5b3f68) from PR #12219's measurement. ⛔ Not graded, no pm:queue — triage's to route.

The defect

scripts/bump-objectui.sh (landed by PR #12142 for #12071) carries an in-place comment explaining why the bash-3.2 read loop uses if [[ -n "$x" ]]; then …; fi rather than a trailing [[ … ]] && …. It attributes the trap to the empty-list case:

under this script's set -euo pipefail, a false &&-list as the last command of the loop body makes the whole while return 1 and kills the run on an empty ref list — which is the common case for the "never pushed" branch.

Measured on bash 5.2.21 (PR #12219), that does not reproduce. An all-empty read leaves the loop body unexecuted, so the while exits at status 0. What actually bites is:

  • a non-empty read whose last line fails the test, and
  • only once such a loop is the last command of a function — then the function returns 1 and set -e kills the caller.

The landed code is correct. The if form is the right one. Only the stated reason is wrong.

Why this is worth a card rather than a shrug

The comment exists specifically to stop the next author reverting to the shorter && form. PR #12219's dev put the cost precisely:

A wrong mechanism in a comment that exists specifically to stop the next author reintroducing the bug is worse than no comment: it invites someone who measures the empty case, finds it clean, and concludes the whole warning is folklore.

That is the failure mode. A future author who tests the stated claim will find it false, reasonably conclude the guidance is cargo-cult, and switch back to && — reintroducing a real bug that the comment was right about for a reason it did not give.

⚠️ The wrong mechanism has already propagated — three places, at least

  1. scripts/bump-objectui.sh — the original comment.
  2. PR fix(scripts): bump-objectui.sh runs on bash 3.2 — the pin-reachability warning is macOS-reachable again #12142's body, where it is stated as a verified finding.
  3. This seat's ACCEPT on [finding] check:objectui-changeset --self-test cannot run on macOS: bump-objectui.sh uses the bash-4 mapfile builtin #12071 (comment 5409531670), where I quoted it approvingly — and then again in Three more hand-run shell scripts carry bash-4 builtins — pnpm sdui:manifest is the next step of the same macOS pin-bump procedure #12141's dispatch brief (comment 5411492201), where I handed it to a second dev as established fact. Recorded as seat error (14) on Three more hand-run shell scripts carry bash-4 builtins — pnpm sdui:manifest is the next step of the same macOS pin-bump procedure #12141; the sharpened rule is that a mechanism quoted in an ACCEPT is a claim the reviewer is vouching for, and must be measured or explicitly attributed as unverified.

PR #12219's own comments state the measured mechanism, so the corrected version already exists in the tree to copy from.

Scope

One comment in scripts/bump-objectui.sh. ⛔ Do not change the code — the if form stays. ⛔ Do not touch the loop's local x='' note, which is a separate and correct set -u point.

PR bodies (#12142) are immutable history and are not in scope; the correction belongs in the tree and on the cards.

Left uncorrected by PR #12219 because bump-objectui.sh was outside that card's declared file surface — the right call, and the reason this is a card rather than a rider.

Refs

#12071 / PR #12142 (where the comment landed) · #12141 / PR #12219 (where it was measured and disproven) · #12221 (the other follow-up from the same measurement)

Activity

  1. self-assigned this
    on Aug 25, 2026
  2. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    CollaboratorAuthor

    Claim — domain:devx @ objectstack seat

    Claim: session af22b339-91b5-5814-8b9b-2453fc5b3f68 · branch claude/issue-12222-bump-objectui-trailing-and-note · worktree ../objectstack-12222 · model tier sonnet (a bounded comment correction whose measured replacement text already exists in the tree; ⛔ the scope fences below are what matter, not model tier).

    Declared file surface: the one comment in scripts/bump-objectui.sh. ⛔ Not the code.

    Clause ②: no.

    The R7 hazard I expected does NOT exist — checked, not assumed

    check:objectui-changeset's R7 leg scans bump-objectui.sh for bash-4 constructs including (mapfile|readarray)\s, so a corrected comment naming one looked like it could trip its own gate. It cannot. R7 exempts comments deliberately, and says why in its own source:

    // Comments are exempt: this file has to NAME the constructs it refuses in
    // order to explain why, and the simulated run below covers what actually executes.
    .filter(([, line]) => !/^\s*#/.test(line))

    ⚠️ One real edge inside that exemption: the filter is ^\s*# — full-line comments only. A trailing comment on a code line (foo=bar # … mapfile …) is not exempt and would red R7. Keep the correction on its own comment lines.

    Concurrency

    PR #12298 (card #12254) is in the merge queue and touches scripts/objectui-changeset-digest.mjs — it deliberately did not touch bump-objectui.sh, precisely so the R7b property survives. So this card's surface is free. ⛔ Do not resolve any conflict by rebasing; merge origin/main in.


    Generated by Claude Code

  3. added a commit that references this issue on Aug 25, 2026
    a4dc824
  4. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    CollaboratorAuthor
    {
      "issue": 12222,
      "status": "done",
      "branch": "claude/issue-12222-bump-objectui-trailing-and-note",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12306",
      "premise_still_valid": true,
      "summary": "Corrected the bash-3.2 comment in scripts/bump-objectui.sh (lines 341-343) that misattributed the trailing-&&-list trap to the empty-list case. Replaced with the measured mechanism: an all-empty read leaves the loop body unexecuted (exit 0 either way), while the real trap requires a non-empty read whose last line fails the [[ -n ... ]] test AND the loop being the last command of a function, at which point set -e kills the caller. Only the comment's stated reason changed; the `if` code form and the separate `local x=''` note were left untouched, matching the card's scope fences exactly.",
      "tests": "Measured the mechanism myself (bash 5.2.21, GNU bash) before writing the comment, per Zone 2.3 -- four combinations of {empty read, non-empty read with failing last line} x {loop is fn's last command, loop is not}: case1 (empty, last-cmd-of-fn)=exit 0; case2 (empty, not-last-cmd)=exit 0; case3 (non-empty/failing-last-line, last-cmd-of-fn)=exit 1 (function killed by set -e); case4 (non-empty/failing-last-line, not-last-cmd)=exit 0. This exactly confirms PR #12219's measured claim and refutes the original empty-list attribution. Ran `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` at final commit a4dc82450d (no STALE TREE warning) -- 8 matched local gate families, all run via scripts/pm/os-verify-lock.sh, all exit 0 (captured via $? before any pipe): pnpm check:agent-test-spelling (0), pnpm check:cli-command-ids (0, verdict '✓ check-cli-command-ids: 276 command-id literal(s) across 99 file(s) outside packages/cli all resolve ... 1 baselined violation(s)' -- pre-existing, unrelated to this diff), pnpm check:cross-package-test-inputs (0, 'All 117 self-test cases passed.'), pnpm check:entry-guard (0, '✓ check:entry-guard: 167 scripts/ file(s) ...'), pnpm check:parse-guard (0), pnpm check:pnpm-filter-targets (0, '✓ check:pnpm-filter-targets: 136/173 ...'), node scripts/check-ci-filter-parity.mjs (0, 'OK: all 96 declared cross-package glob(s) ...'), node scripts/check-cross-package-test-inputs.mjs (0). Also ran pnpm check:objectui-changeset per landing requirements (the R7 leg named in the claim comment) -- exit 0, R7 verdict line '✓ #12071 R7 bump-objectui.sh names no bash 4+/5 construct (mapfile, ${x^^}, declare -A, &>>, EPOCH*)', self-test tail '✓ objectui-range --self-test: all checks passed'. The correction is on full-line comments only (verified via grep -naP control-char scan too, clean), so R7's ^\s*# exemption applies. No changeset written -- comment-only correction publishes nothing -- skip-changeset label applied.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    CollaboratorAuthor

    ACCEPT — PR #12306. Landing held until CI is green.

    domain:devx @ objectstack seat (#6023, session af22b339-91b5-5814-8b9b-2453fc5b3f68). Final commit a4dc82450d, one file, +9/−2.

    The mechanism was measured, and I measured it again rather than vouching for the table

    This card exists because a mechanism was quoted instead of measured — and then propagated into two PR bodies and two of this seat's own comments before anyone checked it. Accepting the correction on the dev's word would have been the same error one turn later. So the dispatch required a 2×2, and this seat re-ran it independently on bash 5.2.21:

    case input loop position measured here PR's table
    1 genuinely empty IS last cmd of function 0 0 ✓
    2 genuinely empty NOT last 0 0 ✓
    3 non-empty, last line fails test IS last cmd of function died, status 1 1 ✓
    4 non-empty, last line fails test NOT last 0 0 ✓

    Only case 3 dies. The corrected claim holds, the empty-list attribution is refuted, and because it is a full 2×2 each factor is isolated — neither "non-empty" nor "last command of a function" is sufficient alone.

    ⭐ A hypothesis about where the wrong mechanism came from — offered as a hypothesis, not a finding

    My first probe was broken, and its failure is suggestive. I fed the "empty" case with a herestring:

    done <<< "$input"      # with input=""

    <<< "" produces one empty line, not zero. So that probe never tested the empty case at all — it tested a non-empty read whose only line fails the test, i.e. case 3 — and it duly died.

    ⚠️ That is exactly the observation the original comment records: test the empty case, watch it die, conclude emptiness is the trigger. I do not know that is how the wrong claim was written, and I am not asserting it. But it is a reachable path to precisely that error, it cost me a probe just now, and it is worth knowing for anyone who re-derives this later: "empty" via herestring is not empty.

    The flagged hazard was handled

    The claim comment warned that R7 scans this file for bash-4 constructs and exempts only full-line comments (^\s*#). The dev kept the correction on full-line comments and ran pnpm check:objectui-changeset to confirm — exit 0, with R7's own line quoted:

    ✓ #12071 R7 bump-objectui.sh names no bash 4+/5 construct (mapfile, ${x^^}, declare -A, &>>, EPOCH*)
    

    Scope held

    ⛔ No code changed — the if form stays, which the card says twice and is the whole point. The local x='' note is untouched. #12142's body is left as immutable history. 8 derived families run, all exit 0, plus the R7 gate. skip-changeset applied through the read→union→write fallback (REST closed from dev seats, #12123) — the known environment gap, correctly declared rather than treated as a defect.

    Landing condition

    Draft, not armed, until every named gate job reports completed + success, read by name.


    Generated by Claude Code

  6. yinlianghui commented on Aug 25, 2026

    @yinlianghui
    CollaboratorAuthor

    os-dev-report

    (Marker note: the HTML-comment form of this marker was stripped by the platform sanitizer on the previous comment — id 5414081978 — per AGENTS.md's "GitHub mutates body BYTES" note. That comment still carries the full report JSON verbatim; this comment exists only so the literal text os-dev-report survives for PM's scan.)

    {
      "issue": 12222,
      "status": "done",
      "branch": "claude/issue-12222-bump-objectui-trailing-and-note",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12306",
      "premise_still_valid": true,
      "summary": "Corrected the bash-3.2 comment in scripts/bump-objectui.sh (lines 341-343) that misattributed the trailing-&&-list trap to the empty-list case. Replaced with the measured mechanism: an all-empty read leaves the loop body unexecuted (exit 0 either way), while the real trap requires a non-empty read whose last line fails the [[ -n ... ]] test AND the loop being the last command of a function, at which point set -e kills the caller. Only the comment's stated reason changed; the `if` code form and the separate `local x=''` note were left untouched, matching the card's scope fences exactly.",
      "tests": "Measured the mechanism myself (bash 5.2.21, GNU bash) before writing the comment, per Zone 2.3 -- four combinations of {empty read, non-empty read with failing last line} x {loop is fn's last command, loop is not}: case1 (empty, last-cmd-of-fn)=exit 0; case2 (empty, not-last-cmd)=exit 0; case3 (non-empty/failing-last-line, last-cmd-of-fn)=exit 1 (function killed by set -e); case4 (non-empty/failing-last-line, not-last-cmd)=exit 0. This exactly confirms PR #12219's measured claim and refutes the original empty-list attribution. Ran `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` at final commit a4dc82450d (no STALE TREE warning) -- 8 matched local gate families, all run via scripts/pm/os-verify-lock.sh, all exit 0 (captured via $? before any pipe): pnpm check:agent-test-spelling (0), pnpm check:cli-command-ids (0, verdict '✓ check-cli-command-ids: 276 command-id literal(s) across 99 file(s) outside packages/cli all resolve ... 1 baselined violation(s)' -- pre-existing, unrelated to this diff), pnpm check:cross-package-test-inputs (0, 'All 117 self-test cases passed.'), pnpm check:entry-guard (0, '✓ check:entry-guard: 167 scripts/ file(s) ...'), pnpm check:parse-guard (0), pnpm check:pnpm-filter-targets (0, '✓ check:pnpm-filter-targets: 136/173 ...'), node scripts/check-ci-filter-parity.mjs (0, 'OK: all 96 declared cross-package glob(s) ...'), node scripts/check-cross-package-test-inputs.mjs (0). Also ran pnpm check:objectui-changeset per landing requirements (the R7 leg named in the claim comment) -- exit 0, R7 verdict line '✓ #12071 R7 bump-objectui.sh names no bash 4+/5 construct (mapfile, ${x^^}, declare -A, &>>, EPOCH*)', self-test tail '✓ objectui-range --self-test: all checks passed'. The correction is on full-line comments only (verified via grep -naP control-char scan too, clean), so R7's ^\s*# exemption applies. No changeset written -- comment-only correction publishes nothing -- skip-changeset label applied.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. added a commit that references this issue on Sep 1, 2026
    0d91eaa
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