Skip to content

fix(build): seal lite-scons harness JSON channel against user-script stdout#946

Merged
zackees merged 2 commits into
mainfrom
fix/945-lite-scons-stdout
Jul 2, 2026
Merged

fix(build): seal lite-scons harness JSON channel against user-script stdout#946
zackees merged 2 commits into
mainfrom
fix/945-lite-scons-stdout

Conversation

@zackees

@zackees zackees commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

lite_scons_harness.py emitted its JSON result on the same stdout that user extra_scripts print to, so any script banner (or a subprocess inheriting the raw fd) corrupted the protocol and failed the build with failed to parse extra_scripts runtime output: expected value at line 1 column 1.

Fix (harness-side, keeping the JSON boundary unambiguous):

  • Duplicate the real stdout fd at startup as the protocol channel; repoint fd 1 at stderr for the entire run, so even raw-fd subprocess writers can't touch the JSON.
  • Exec each user script under contextlib.redirect_stdout; captured text is preserved in notes (script stdout (<name>): ..., truncated to 4 KiB) and echoed to stderr for verbose logs.

Validation

  • New test test_resolve_extra_script_overlay_tolerates_user_stdout_noise — a script that prints at import time, spawns a subprocess that writes to the inherited stdout fd, then appends a CPPDEFINE; asserts the overlay resolves and carries the define. All 14 script_runtime tests + 5 lite_scons_acceptance tests pass (Linux container, soldr cargo).
  • Real-workload check: NightDriverStrip env:demo's four extra_scripts (install_intelhex, pio_audit, bake_site, merge_image) previously reproduced the failure; with the fix the harness returns clean JSON, unsupported: [], with the audit/bake banners captured in notes.

Closes #945
Part of #942

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of user script output so extra console noise no longer interferes with project setup or generated results.
    • Strengthened the way the tool separates its structured output from regular terminal messages, preventing corrupted responses.
  • Tests

    • Added coverage for projects that print text and spawn subprocesses during script execution, confirming the setup still completes correctly.

…stdout

User extra_scripts routinely print() progress banners (NightDriverStrip's
pio_audit.py / bake_site.py do), and some spawn subprocesses that write
to the inherited stdout fd; both landed ahead of the harness's JSON
payload and broke overlay parsing with 'expected value at line 1
column 1'. Duplicate the real stdout for the protocol at startup, point
fd 1 at stderr for the whole run, and capture each script's Python-level
stdout into notes (truncated) with a stderr echo for verbose logs.

Validated against NightDriverStrip env:demo's four extra_scripts in the
#942 Docker harness: clean JSON, unsupported=[], banners preserved in
notes.

Closes #945
Part of #942

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@zackees, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9d5b50c-3b5d-4a6a-bc6b-bd1e73bba843

📥 Commits

Reviewing files that changed from the base of the PR and between 413b0a8 and 79e51a0.

📒 Files selected for processing (2)
  • crates/fbuild-build/src/lite_scons_harness.py
  • crates/fbuild-build/src/script_runtime_tests.rs
📝 Walkthrough

Walkthrough

The harness script lite_scons_harness.py now captures stdout output from user extra_scripts into ledger.notes and stderr, and seals the JSON protocol by writing final output through a duplicated file descriptor rather than direct stdout. A new Rust test validates the fix against stdout noise from scripts and subprocesses.

Changes

Stdout protocol sealing fix

Layer / File(s) Summary
Script output capture helper
crates/fbuild-build/src/lite_scons_harness.py
Adds contextlib/io imports and a new run_script_captured function that redirects stdout during script execution, mirroring captured output to stderr and appending it to ledger.notes.
Protocol fd sealing and JSON emission
crates/fbuild-build/src/lite_scons_harness.py
main() duplicates the original stdout fd, redirects process stdout to stderr, switches pre/post script execution to run_script_captured, and emits the final JSON via the duplicated fd instead of sys.stdout.
Regression test for stdout noise tolerance
crates/fbuild-build/src/script_runtime_tests.rs
Adds a tokio async test with an extra_scripts file that prints banners and spawns a stdout-writing subprocess, asserting resolve_extra_script_overlay still yields the expected compile flag.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Rust as script_runtime.rs
  participant Harness as lite_scons_harness.py
  participant Script as extra_script
  participant Notes as ledger.notes

  Rust->>Harness: invoke harness process
  Harness->>Harness: duplicate stdout fd, redirect stdout to stderr
  Harness->>Script: run_script_captured(env, ledger, path)
  Script-->>Script: print() / subprocess writes to stdout
  Script->>Notes: capture output appended to notes
  Harness->>Rust: write json.dump via duplicated fd
  Rust->>Rust: parse clean JSON response
Loading

Compact metadata

  • Related issues: #945 (fix lite-scons harness JSON protocol corruption from user-script print())
  • Related PRs: None mentioned
  • Suggested labels: bug, build
  • Suggested reviewers: None specified

🐰 A script that prints, a banner unfurled,
once broke the JSON in a stdout world.
Now buffers catch each noisy line,
while duplicated fds keep the protocol fine.
Clean parse restored — hop, all is well!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main harness-side fix.
Linked Issues check ✅ Passed The harness isolates JSON output, preserves user-script stdout in notes, and adds a regression test for printing scripts and subprocess noise.
Out of Scope Changes check ✅ Passed The diff stays focused on the lite-scons harness and its regression test, with no clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/945-lite-scons-stdout

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

🧹 Nitpick comments (1)
crates/fbuild-build/src/script_runtime_tests.rs (1)

361-364: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert captured stdout is preserved in overlay.notes.

This verifies the second half of the contract: stdout noise should not corrupt JSON and should remain available in the result payload.

Test assertion to add
     assert!(overlay
         .global_compile
         .common
         .contains(&"-DPRINT_NOISE_OK".to_string()));
+    assert!(overlay
+        .notes
+        .iter()
+        .any(|note| note.contains("loud import-time banner")));
🤖 Prompt for 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.

In `@crates/fbuild-build/src/script_runtime_tests.rs` around lines 361 - 364, The
test currently only checks that `-DPRINT_NOISE_OK` is added to
`overlay.global_compile.common`, but it does not verify that captured stdout is
preserved in `overlay.notes`. Update the `script_runtime_tests` case to also
assert that the result payload’s `overlay.notes` includes the stdout noise
captured by the script runtime, using the existing `overlay` value in this test
to validate the contract end-to-end.
🤖 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 `@crates/fbuild-build/src/lite_scons_harness.py`:
- Around line 684-701: `run_script_captured` only records Python-level stdout
from `contextlib.redirect_stdout`, so fd-level writes from subprocesses and
`os.write(1, ...)` are lost from `ledger.notes`. Update `run_script_captured` to
temporarily capture fd 1 to a temp file while `run_script(env, path)` runs, then
merge that fd-level output with the existing `buf` contents and append the
combined text to `ledger.notes` (still echoing to `stderr` as needed). Use the
`run_script_captured` wrapper and the `ledger.notes.append(...)` path as the
main points to locate the fix.

---

Nitpick comments:
In `@crates/fbuild-build/src/script_runtime_tests.rs`:
- Around line 361-364: The test currently only checks that `-DPRINT_NOISE_OK` is
added to `overlay.global_compile.common`, but it does not verify that captured
stdout is preserved in `overlay.notes`. Update the `script_runtime_tests` case
to also assert that the result payload’s `overlay.notes` includes the stdout
noise captured by the script runtime, using the existing `overlay` value in this
test to validate the contract end-to-end.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46638990-e99e-4f8e-905b-bc58835399d4

📥 Commits

Reviewing files that changed from the base of the PR and between 1294ed1 and 413b0a8.

📒 Files selected for processing (2)
  • crates/fbuild-build/src/lite_scons_harness.py
  • crates/fbuild-build/src/script_runtime_tests.rs

Comment thread crates/fbuild-build/src/lite_scons_harness.py
CodeRabbit review: subprocess/os.write output bypassed the Python-level
redirect and reached only stderr, dropping it from the notes contract.
Swap a temp file onto fd 1 per script via dup2 and fold both capture
layers into the notes entry; test now asserts both the print() banner
and the subprocess raw-fd noise are preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zackees zackees merged commit 7b8a5d0 into main Jul 2, 2026
87 of 92 checks passed
@zackees zackees deleted the fix/945-lite-scons-stdout branch July 2, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

fix(build): lite-scons harness JSON protocol is corrupted by user-script print()

1 participant