fix: leverage sensors write state JSON with a trailing newline (BRO-1973, v0.37.1)#97
Conversation
…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>
📝 WalkthroughWalkthroughBoth 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. ChangesState output newline contract
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
CHANGELOG.mdVERSIONscripts/leverage-sensor.pyscripts/leverage-ship-sensor.pytests/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>
|
Accepted the ship-sensor asymmetry finding — real gap, fixed in 26973b4. Factored the byte count into Mutation re-verified: reverting |
…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>
Closes BRO-1973 — https://linear.app/broomva/issue/BRO-1973
The defect
leverage-sensor.py:364andleverage-ship-sensor.py:330both serialize their snapshot withjson.dump(..., indent=2)and stop — no POSIX trailing newline. The sibling JSONL write in the very samestore()(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.jsonor.control/leverage-ship-state.jsontherefore goes git-dirty after every session, and formatter gates (biome / ultracite / prettier) reportFormatter would have printed …— sobun run lintfails spuriously on a repo that is otherwise green.Observed live downstream: a composed-on-
mainverify failed on lint for exactly this and nothing else. Byte-confirmed withod -c— committed copy ends7d0a 7d0a, working copy ends7d0a 7d.The ship sensor was not in the original report; it is the same defect at
leverage-ship-sensor.py:330and 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 freshbstack bootstrapthat commits its state hits it.The fix
f.write("\n")after eachjson.dump. The ship sensor's write stays inside itstempfile+os.replaceatomic 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 zeroghcalls: no network, no auth required in CI):.control/*.jsonsnapshots end with0x0a(checked viatail -c 1 | od, not a text-mode read, so an editor cannot mask it).control/leverage-metrics.jsonlstill ends with\n— guards the sibling write that was already correct"w", so this catches an append/truncate confusion.ship-*.tmpbehindTest plan
tests/leverage-state-trailing-newline.test.shtests/*.test.sh(37 files)tests/canary/*.test.sh(5 files)bash scripts/doctor.sh --quietshellcheck --severity=warningon the new testpython3 -m aston both sensorsMutation proof — each fix is independently load-bearing:
leverage-sensor.py'sf.write("\n")→ 3 failures, all in the main-sensor block (5 passed, 3 failed)leverage-ship-sensor.py'sf.write("\n")→ 1 failure, in the ship block (7 passed, 1 failed)8 passed, 0 failedRelease
Patch bump
0.37.0→0.37.1perRELEASE.md("Bug fixes … safe to auto-upgrade"), with the matching## 0.37.1CHANGELOG section forvalidate-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.shor the canary suite inside the bstack checkout itself generates untrackedMETALAYER.md+schemas/*.jsonin the working tree. Deliberately left out of this diff; worth a.gitignoreentry or a--workspaceredirect in a follow-up.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Release