Skip to content

fix(ci): restore green main — dylint allowlist, spawn markers, macOS unused_mut, rustdoc links - #1216

Merged
zackees merged 1 commit into
mainfrom
fix/restore-green-main
Jul 31, 2026
Merged

fix(ci): restore green main — dylint allowlist, spawn markers, macOS unused_mut, rustdoc links#1216
zackees merged 1 commit into
mainfrom
fix/restore-green-main

Conversation

@zackees

@zackees zackees commented Jul 31, 2026

Copy link
Copy Markdown
Member

Why

main is red on six checks. None of them relate to each other; they accumulated because the workflows that run them are pull_request-triggered, so nothing re-ran them on main after the merges that broke them (#1197 ide/clangd, #1206 debug, #1207 serial sysfs enrichment).

Found while verifying an unrelated dependency bump (#1215) — every failure there reproduced identically on main.

What this fixes

1. Dylint — stale allowlist path

dylints/ban_unrooted_tempdir/src/allowlist.txt:53 still listed crates/fbuild-cli/src/cli/clangd_config.rs, deleted in #1197. Entry removed.

2. Lint subprocess spawns — 3 unannotated Command::new

Annotated with allow-direct-spawn: markers; each is a deliberate direct spawn:

  • clangd_check_parity.rs:242 — integration test driver invoking the clangd binary.
  • debug.rs:539 — interactive gdb must inherit the user's terminal stdio; the capturing helpers would break the session.
  • ide.rs:311 — editor launched detached, must outlive the CLI.

Note for future annotators: the scanner only checks the hit line and the one line immediately above, so the marker has to be a single line. A wrapped multi-line comment silently fails to register.

This also fixes Check (ubuntu-latest), which runs the same script as a step.

3. Check (macos-latest) — unused_mut

ports.rs bound let mut ports unconditionally, but only cfg(target_os = "linux") mutates it (sysfs health enrichment), so every other unix target tripped -D unused-mut. The mut now exists only inside the Linux cfg, via a shadowing block — no #[allow].

4. Documentation — 6 rustdoc errors across 3 crates

  • fbuild-daemon boards.rs/libraries.rs: URL templates like ?query=<optional filter> parsed as unclosed HTML tags. Wrapped in backticks.
  • fbuild-serial ports.rs/sysfs_usb.rs: intra-doc links to cfg(target_os = "linux") items, which do not exist to resolve against when docs build on a non-Linux host, plus one link to a private item.
  • fbuild-build-engine clang.rs/source_scanner.rs: three links to private items.

Unresolvable links became code spans, each with a short note saying why it is deliberately not a link, so nobody "helpfully" converts them back.

Not fixed here

build / build (CH32V006) is also red on main, but it is a vendor-C compile error (implicit declaration of function 'SetSysClockTo_48MHZ_HSI') in the openwch Arduino core, not CI hygiene. Out of scope — left for a targeted board fix.

Verification

Locally on Windows, all exit 0:

  • uv run python ci/check_dylint_allowlists.py
  • uv run python ci/find_direct_subprocess.py --fail
  • RUSTDOCFLAGS="-D warnings" soldr cargo doc --workspace --no-deps
  • soldr cargo clippy --workspace --all-targets -- -D warnings
  • soldr cargo fmt --all -- --check
  • bash test

Caveat on the macOS fix: it could not be verified locally — cross-compiling fbuild-serial to x86_64-apple-darwin fails on ring's build script needing a macOS cc. The cfg-shadowing pattern was verified in an isolated cargo project (clean under -D warnings with the cfg both on and off), but Check (macos-latest) on this PR is the real proof.

… unused_mut, rustdoc links

main is red on six checks. They are unrelated to each other and accumulated
because the workflows that run them are `pull_request`-triggered, so nothing
re-ran them on main after the merges that broke them (#1197, #1206, #1207).
Found while verifying an unrelated dependency bump (#1215); every failure
there reproduced identically on main.

Dylint: drop `crates/fbuild-cli/src/cli/clangd_config.rs` from
ban_unrooted_tempdir's allowlist -- the file was deleted in #1197.

Lint subprocess spawns: annotate three deliberate direct spawns with
`allow-direct-spawn:` markers (clangd test driver, interactive gdb that must
inherit terminal stdio, detached editor launch that must outlive the CLI).
The scanner only inspects the hit line and the one line directly above it, so
the marker must be a single line -- a wrapped comment silently fails to
register. This also fixes Check (ubuntu-latest), which runs the same script.

Check (macos-latest): `ports.rs` bound `let mut ports` unconditionally while
only `cfg(target_os = "linux")` mutates it, so every other unix target tripped
`-D unused-mut`. The `mut` now exists only inside the Linux cfg via a
shadowing block, rather than an `#[allow]`.

Documentation: six rustdoc errors across three crates. URL templates such as
`?query=<optional filter>` parsed as unclosed HTML tags; several intra-doc
links pointed at `cfg(target_os = "linux")` items that do not exist to resolve
against when docs build on a non-Linux host; three more pointed at private
items. Unresolvable links become code spans, each with a short note on why it
is deliberately not a link.

CH32V006 (`build / build`) is also red on main but is a vendor-C compile error
in the openwch Arduino core, not CI hygiene, and is left for a targeted fix.

Verified locally on Windows, all exit 0: check_dylint_allowlists.py,
find_direct_subprocess.py --fail, cargo doc with RUSTDOCFLAGS=-D warnings,
clippy -D warnings, cargo fmt --check, and `bash test` (77 suites, 0 failed).

The macOS fix could not be verified locally: cross-compiling fbuild-serial to
x86_64-apple-darwin fails on ring's build script needing a macOS `cc`. The
cfg-shadowing pattern was verified in an isolated cargo project (clean under
-D warnings with the cfg both on and off); Check (macos-latest) on this PR is
the real proof.

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

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 25 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 Plus

Run ID: 054c9fed-f9c9-4daf-a776-1abbdf31c106

📥 Commits

Reviewing files that changed from the base of the PR and between bc23392 and 1b76093.

📒 Files selected for processing (10)
  • crates/fbuild-build-engine/src/compile_database/clang.rs
  • crates/fbuild-build-engine/src/source_scanner.rs
  • crates/fbuild-build/tests/clangd_check_parity.rs
  • crates/fbuild-cli/src/cli/debug.rs
  • crates/fbuild-cli/src/cli/ide.rs
  • crates/fbuild-daemon/src/handlers/boards.rs
  • crates/fbuild-daemon/src/handlers/libraries.rs
  • crates/fbuild-serial/src/ports.rs
  • crates/fbuild-serial/src/sysfs_usb.rs
  • dylints/ban_unrooted_tempdir/src/allowlist.txt

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.

@zackees
zackees merged commit 093f518 into main Jul 31, 2026
90 of 93 checks passed
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Aug 1, 2026
zackees added a commit that referenced this pull request Aug 1, 2026
Neither test was a regression; both surfaced once #1216/#1221 stopped the
macOS job aborting before it reached the test step. Both assert exact
outcomes over real OS resources under timing pressure.

## spawn_lock_is_exclusive_within_process

`try_acquire_spawn_lock_at` used `.ok().flatten()`, collapsing a hard I/O
error into the same `None` that means "another process holds the lock" — so
when the third acquire failed, there was no way to tell which had happened.

- Split into `try_acquire_spawn_lock_result_at` (propagates) and the existing
  swallowing wrapper (logs errno + kind, then returns `None`). The
  "a broken filesystem must never gate progress" contract for the production
  caller is unchanged; the failure is just no longer silent.
- The test now uses the error-preserving variant, so a hard error reports the
  actual errno instead of a bare `assertion failed`.
- Retry `EINTR` in `file_lock::try_acquire`, around both the `open` and the
  `flock`. `flock()` is interruptible by signals on macOS/BSD and fs2 returns
  the raw OS error, which our classifier doesn't recognize as contention — so
  an EINTR became a hard error. Correct regardless of whether it was the cause
  here.

## streaming_download_retries_chunk_stalls_five_times_without_output

This was the only test combining `#[tokio::test(start_paused = true)]` with a
real `TcpListener`, and it was the one that flaked. Paused time auto-advances
whenever the runtime looks idle, but socket readiness comes from the OS
reactor, not the virtual clock — so with the client parked on `chunk()` and
the server on `sleep`, the clock could jump past a connection that was about
to reach `accept()`, leaving `request_count` short of 5.

Took the issue's preferred fix rather than the stopgap: extract the retry
durations into a `RetryTiming` struct so the test drives the *same* retry
logic on millisecond durations in real time. No `start_paused`, no reactor
race, and the assertion stays at exactly 5 attempts instead of being weakened
to `>= 1`. Runs in ~1s.

`production_retry_timing_matches_the_constants` pins the seam to the real
constants so the injection can't drift into a behavior change.

Verified: the chunk-stall test passes 5/5 consecutive runs locally; clippy
clean on all four crates.

Co-Authored-By: Claude <noreply@anthropic.com>
zackees added a commit that referenced this pull request Aug 1, 2026
Neither test was a regression; both surfaced once #1216/#1221 stopped the
macOS job aborting before it reached the test step. Both assert exact
outcomes over real OS resources under timing pressure.

## spawn_lock_is_exclusive_within_process

`try_acquire_spawn_lock_at` used `.ok().flatten()`, collapsing a hard I/O
error into the same `None` that means "another process holds the lock" — so
when the third acquire failed, there was no way to tell which had happened.

- Split into `try_acquire_spawn_lock_result_at` (propagates) and the existing
  swallowing wrapper (logs errno + kind, then returns `None`). The
  "a broken filesystem must never gate progress" contract for the production
  caller is unchanged; the failure is just no longer silent.
- The test now uses the error-preserving variant, so a hard error reports the
  actual errno instead of a bare `assertion failed`.
- Retry `EINTR` in `file_lock::try_acquire`, around both the `open` and the
  `flock`. `flock()` is interruptible by signals on macOS/BSD and fs2 returns
  the raw OS error, which our classifier doesn't recognize as contention — so
  an EINTR became a hard error. Correct regardless of whether it was the cause
  here.

## streaming_download_retries_chunk_stalls_five_times_without_output

This was the only test combining `#[tokio::test(start_paused = true)]` with a
real `TcpListener`, and it was the one that flaked. Paused time auto-advances
whenever the runtime looks idle, but socket readiness comes from the OS
reactor, not the virtual clock — so with the client parked on `chunk()` and
the server on `sleep`, the clock could jump past a connection that was about
to reach `accept()`, leaving `request_count` short of 5.

Took the issue's preferred fix rather than the stopgap: extract the retry
durations into a `RetryTiming` struct so the test drives the *same* retry
logic on millisecond durations in real time. No `start_paused`, no reactor
race, and the assertion stays at exactly 5 attempts instead of being weakened
to `>= 1`. Runs in ~1s.

`production_retry_timing_matches_the_constants` pins the seam to the real
constants so the injection can't drift into a behavior change.

Verified: the chunk-stall test passes 5/5 consecutive runs locally; clippy
clean on all four crates.

Co-Authored-By: Claude <noreply@anthropic.com>
zackees added a commit that referenced this pull request Aug 1, 2026
Neither test was a regression; both surfaced once #1216/#1221 stopped the
macOS job aborting before it reached the test step. Both assert exact
outcomes over real OS resources under timing pressure.

## spawn_lock_is_exclusive_within_process

`try_acquire_spawn_lock_at` used `.ok().flatten()`, collapsing a hard I/O
error into the same `None` that means "another process holds the lock" — so
when the third acquire failed, there was no way to tell which had happened.

- Split into `try_acquire_spawn_lock_result_at` (propagates) and the existing
  swallowing wrapper (logs errno + kind, then returns `None`). The
  "a broken filesystem must never gate progress" contract for the production
  caller is unchanged; the failure is just no longer silent.
- The test now uses the error-preserving variant, so a hard error reports the
  actual errno instead of a bare `assertion failed`.
- Retry `EINTR` in `file_lock::try_acquire`, around both the `open` and the
  `flock`. `flock()` is interruptible by signals on macOS/BSD and fs2 returns
  the raw OS error, which our classifier doesn't recognize as contention — so
  an EINTR became a hard error. Correct regardless of whether it was the cause
  here.

## streaming_download_retries_chunk_stalls_five_times_without_output

This was the only test combining `#[tokio::test(start_paused = true)]` with a
real `TcpListener`, and it was the one that flaked. Paused time auto-advances
whenever the runtime looks idle, but socket readiness comes from the OS
reactor, not the virtual clock — so with the client parked on `chunk()` and
the server on `sleep`, the clock could jump past a connection that was about
to reach `accept()`, leaving `request_count` short of 5.

Took the issue's preferred fix rather than the stopgap: extract the retry
durations into a `RetryTiming` struct so the test drives the *same* retry
logic on millisecond durations in real time. No `start_paused`, no reactor
race, and the assertion stays at exactly 5 attempts instead of being weakened
to `>= 1`. Runs in ~1s.

`production_retry_timing_matches_the_constants` pins the seam to the real
constants so the injection can't drift into a behavior change.

Verified: the chunk-stall test passes 5/5 consecutive runs locally; clippy
clean on all four crates.

Co-Authored-By: Claude <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

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant