Skip to content

fix: leverage sensors write state JSON with a trailing newline (BRO-1973, v0.37.1)#97

Merged
broomva merged 2 commits into
mainfrom
fix/bro-1973-leverage-state-trailing-newline
Jul 24, 2026
Merged

fix: leverage sensors write state JSON with a trailing newline (BRO-1973, v0.37.1)#97
broomva merged 2 commits into
mainfrom
fix/bro-1973-leverage-state-trailing-newline

Conversation

@broomva

@broomva broomva commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Closes BRO-1973 — https://linear.app/broomva/issue/BRO-1973

The defect

leverage-sensor.py:364 and leverage-ship-sensor.py:330 both serialize their snapshot with json.dump(..., indent=2) and stop — no POSIX trailing newline. The sibling JSONL write in the very same store() (f.write(json.dumps(record) + "\n")) has always been correct, so this is an inconsistency inside one function, not a deliberate format choice.

A bstack-governed repo that tracks .control/leverage-state.json or .control/leverage-ship-state.json therefore goes git-dirty after every session, and formatter gates (biome / ultracite / prettier) report Formatter would have printed … — so bun run lint fails spuriously on a repo that is otherwise green.

Observed live downstream: a composed-on-main verify failed on lint for exactly this and nothing else. Byte-confirmed with od -c — committed copy ends 7d0a 7d0a, working copy ends 7d0a 7d.

The ship sensor was not in the original report; it is the same defect at leverage-ship-sensor.py:330 and was reproducing live in the same downstream repo at the time of writing.

.gitignore-ing the state file masks the symptom rather than fixing it — the sensor is emitting a malformed text file either way, and any fresh bstack bootstrap that commits its state hits it.

The fix

f.write("\n") after each json.dump. The ship sensor's write stays inside its tempfile + os.replace atomic swap, so a concurrent reader still never observes a partially-written file.

New regression test

tests/leverage-state-trailing-newline.test.sh — 10 assertions, fully hermetic (the ship sensor is driven with an empty repo allowlist, so it makes zero gh calls: no network, no auth required in CI):

  • both .control/*.json snapshots end with 0x0a (checked via tail -c 1 | od, not a text-mode read, so an editor cannot mask it)
  • .control/leverage-metrics.jsonl still ends with \n — guards the sibling write that was already correct
  • both files still parse as JSON with the newline appended
  • exactly one trailing newline after a second run — the state file uses "w", so this catches an append/truncate confusion
  • the ship sensor's atomic swap leaves no .ship-*.tmp behind

Test plan

Gate Result
tests/leverage-state-trailing-newline.test.sh 8 passed, 0 failed
Full tests/*.test.sh (37 files) all pass
tests/canary/*.test.sh (5 files) all pass
bash scripts/doctor.sh --quiet 100/100 checks passed
shellcheck --severity=warning on the new test clean
python3 -m ast on both sensors parses

Mutation proof — each fix is independently load-bearing:

  • revert only leverage-sensor.py's f.write("\n") → 3 failures, all in the main-sensor block (5 passed, 3 failed)
  • revert only leverage-ship-sensor.py's f.write("\n") → 1 failure, in the ship block (7 passed, 1 failed)
  • both restored → 8 passed, 0 failed

Release

Patch bump 0.37.00.37.1 per RELEASE.md ("Bug fixes … safe to auto-upgrade"), with the matching ## 0.37.1 CHANGELOG section for validate-release.yml.

Migration

None. Existing tracked state files pick up the newline on the next sensor run — a one-line diff to commit once, after which the file stops moving.

Noted, not fixed here

Running scripts/doctor.sh or the canary suite inside the bstack checkout itself generates untracked METALAYER.md + schemas/*.json in the working tree. Deliberately left out of this diff; worth a .gitignore entry or a --workspace redirect in a follow-up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Sensor-generated JSON state files now consistently end with a standard trailing newline.
    • Repeated sensor runs no longer add extra blank lines or leave temporary files behind.
    • Existing state files are updated automatically on the next sensor run; no migration is required.
  • Tests

    • Added regression coverage for newline formatting, JSON validity, repeated writes, and atomic state updates.
  • Release

    • Updated the release version to 0.37.1.

…973)

Both leverage-sensor.py and leverage-ship-sensor.py serialized their snapshot
with json.dump(..., indent=2) and stopped, emitting a file with no POSIX
trailing newline. The sibling JSONL write in the same store() has always been
correct, so this was an inconsistency rather than a format choice.

A governed repo that TRACKS .control/leverage-state.json or
.control/leverage-ship-state.json therefore went git-dirty after every session,
and formatter gates (biome / ultracite / prettier) failed on a repo that was
otherwise green. Observed live downstream: a composed-on-main verify failed on
lint for exactly this and nothing else.

The ship sensor's write stays inside its tempfile + os.replace atomic swap, so
a concurrent reader never observes a partially-written file.

tests/leverage-state-trailing-newline.test.sh drives both sensors hermetically
(the ship sensor with an empty repo allowlist, so zero gh calls) and asserts
every emitted .control/ file ends with 0x0a, still parses as JSON, carries
exactly one trailing newline across repeated runs, and leaves no .ship-*.tmp
behind. Mutation-proven: reverting either f.write("\n") turns it red, each on
its own assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Both leverage sensors now append POSIX trailing newlines to serialized JSON state output. A regression test verifies newline presence, JSON parsing, repeated rewrites, and atomic temporary-file cleanup. Release metadata is updated to version 0.37.1.

Changes

State output newline contract

Layer / File(s) Summary
Sensor JSON newline writes
scripts/leverage-sensor.py, scripts/leverage-ship-sensor.py, tests/leverage-state-trailing-newline.test.sh
Both sensors append trailing newlines to JSON output. The regression test validates state and metrics files, repeated rewrites, JSON parsing, and cleanup of temporary ship-state files.
Release metadata
VERSION, CHANGELOG.md
The release version changes to 0.37.1, and the changelog documents the newline fix and regression test.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: both leverage sensors now write state JSON with a trailing newline.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bro-1973-leverage-state-trailing-newline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/leverage-state-trailing-newline.test.sh`:
- Around line 89-90: Extend the repeated-run scenario in the trailing-newline
test to invoke the ship sensor a second time, then use the exact-one-newline
assertion for leverage-ship-state.json. Keep the existing leverage-state.json
check and reuse the test’s established assert_newline helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 08129b21-e26e-4252-b699-459de5a7061e

📥 Commits

Reviewing files that changed from the base of the PR and between a2fb3a1 and e5c180f.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • VERSION
  • scripts/leverage-sensor.py
  • scripts/leverage-ship-sensor.py
  • tests/leverage-state-trailing-newline.test.sh

Comment thread tests/leverage-state-trailing-newline.test.sh
…97)

The exactly-one-trailing-newline assertion covered only leverage-state.json, so
a regression in the ship writer's rewrite path could have passed. Factored the
byte count into assert_single_trailing_newline() and applied it to both.

The ship writer swaps a fresh tempfile in rather than reopening the target, so
it is structurally immune today — the point is that a future refactor away from
os.replace cannot regress it silently.

Mutation re-verified: reverting the ship sensor's f.write("\n") now fails 3
assertions (was 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@broomva

broomva commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Accepted the ship-sensor asymmetry finding — real gap, fixed in 26973b4.

Factored the byte count into assert_single_trailing_newline() and applied it to both sensors. The ship writer swaps a fresh tempfile in via os.replace rather than reopening the target, so it is structurally immune today; the assertion exists so a future refactor away from os.replace cannot regress it silently.

Mutation re-verified: reverting leverage-ship-sensor.py's f.write("\n") now fails 3 assertions (was 1). Suite is 10 passed / 0 failed.

@broomva
broomva merged commit e0ad2c8 into main Jul 24, 2026
6 checks passed
@broomva
broomva deleted the fix/bro-1973-leverage-state-trailing-newline branch July 24, 2026 02:59
broomva added a commit to broomva/maestro that referenced this pull request Jul 24, 2026
…drift (BRO-1973) (#101)

This repo COPIES 14 scripts from github.com/broomva/bstack, because the hooks in
.claude/settings.json resolve $CLAUDE_PROJECT_DIR/scripts/<hook> and so must exist
in a fresh clone, in CI, and in a worktree with no user-level bstack install. That
is a deliberate tradeoff, but the copies had no check, so they rotted:

- leverage-sensor.py and leverage-ship-sensor.py sat one commit behind a real bug
  fix (state JSON written with no trailing newline → a repo that TRACKS the file
  goes git-dirty every session and fails its own formatter gate). Both are now
  byte-identical to broomva/bstack#97.
- l3-stability-pretool-hook.sh is genuinely stale: it predates bstack's BRO-1926
  scope guard and points at a ./bin/bstack that does not exist here. Left alone —
  it is a live PreToolUse gate with its own blast radius. Recorded as a divergence
  and tracked on BRO-1974.
- The other four divergences are cosmetic vendor-time header rewrites.

scripts/check-bstack-vendor.ts is the check that goes red on divergence. Offline by
default (in `bun run check` and the CI quality job): every vendored file must hash
to what scripts/bstack-vendor.json records, every difference from upstream must
carry a written reason, no note may outlive the difference it explains, and no new
script may appear in scripts/ unaccounted for. `--upstream` fetches the pinned ref
to detect bstack moving ahead; kept out of the required gate so CI stays offline.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant