Skip to content

fix(unwrap): production unwrap sweep + ban_unwrap_in_production rename (#844 item 11) - #851

Merged
zackees merged 1 commit into
mainfrom
fix/unwrap-prod-sweep-844
Jun 29, 2026
Merged

fix(unwrap): production unwrap sweep + ban_unwrap_in_production rename (#844 item 11)#851
zackees merged 1 commit into
mainfrom
fix/unwrap-prod-sweep-844

Conversation

@zackees

@zackees zackees commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Closes #844 item 11. After accurate filtering (excluding #[cfg(test)] mod blocks and sibling test files like tests.rs/*_tests.rs), the production unwrap count was ~30 — not 2,238. All migrated, lint renamed and widened.

Migrations (28 sites across 21 files)

Per-file approach:

  • fbuild-python/serial_monitor.rs (12): Mutex::lockunwrap_or_else(|e| e.into_inner()) for poison tolerance; serde to_stringexpect("ClientMessage X serialization is infallible")
  • fbuild-serial/crash_decoder.rs (5): static regex compiles → expect("static <name> pattern is a valid regex")
  • fbuild-deploy/esp32/image.rs (3): 4-byte slice converts → expect("4-byte slice converts to [u8; 4] (bounds checked above)")
  • fbuild-daemon/handlers/websockets.rs (3): SerialServerMessage serde → expect("infallible")
  • fbuild-config/bin/enrich_boards.rs (3): path utilities → expect("board JSON path always has a file stem")
  • fbuild-python/async_serial_monitor.rs (2): ClientMessage serde → expect("infallible")
  • fbuild-header-scan/scanner.rs (2): parser invariants → expect("is_raw_string_open guarantees ' ' / '(' ahead")
  • fbuild-build/compiler.rs (2): COMPILER_IDENTITY_CACHE.lock() → poison-tolerant
  • fbuild-build/pipeline/compile.rs (2): same poison-tolerant pattern
  • fbuild-cli/cli/clang_tools.rs (2): semaphore expect("never closed before all tasks finish")
  • fbuild-daemon/main.rs (2): static CORS origin → expect("'http://localhost' is a valid CORS origin")
  • 1 site converted to ? (deploy pipeline closure already returns Result)

Lint extension

dylints/ban_unwrap_in_daemon_handlers/ renamed to dylints/ban_unwrap_in_production/:

  • Scope widened from crates/fbuild-daemon/src/handlers/** to ALL of crates/fbuild-daemon/src/** AND crates/fbuild-cli/src/cli/**
  • Sibling-test detection tightened from substring name.contains("tests") to strict patterns tests.rs / *_tests.rs / tests_*.rs (the old substring match would have accidentally exempted production files like run_tests_pipeline.rs)
  • Workspace Cargo.toml exclude, ci/hooks/crate_guard.py allowlist, dylints/README.md row, and cross-references all updated

Test plan

  • soldr cargo check --workspace --all-targets — clean
  • soldr cargo clippy --workspace --all-targets -- -D warnings — clean
  • soldr cargo test --workspace --no-fail-fast --lib — passes for every touched crate. The 3 pre-existing fbuild-daemon handlers::{health,locks}::tests::... failures (panic at status_manager.rs:221: "can call blocking only when running on the multi-threaded runtime") reproduce identically on origin/main and are unrelated to this change.

Closes #844.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Expanded the custom lint to cover broader production code, not just daemon handlers.
    • Added guidance for replacing unsafe .unwrap() usage with safer alternatives and clearer error handling.
  • Bug Fixes

    • Improved resilience across build, daemon, CLI, config, Python, and serial-related flows by avoiding panics when shared state is unexpectedly poisoned or serialization/parsing fails.
    • Some error messages are now more explicit, making failures easier to understand.

…#844)

Closes #844 item 11.

## Migration sweep (~28 sites across 14 files)

Removed every `.unwrap()` from fbuild production code outside of
`#[cfg(test)]` modules and sibling test files. Strategy per site:

* serde JSON serialization of typed structs/enums (`ClientMessage`,
  `SerialServerMessage`) — `.expect("CRATE: <type> serialization is
  infallible")`. These types contain only primitives + collections, so
  serialization cannot fail.
* `Mutex::lock()` / `RwLock::write()` — `.unwrap_or_else(|e| e.into_inner())`
  for poison-tolerant lock acquisition (matches the `ban_poison_panic`
  dylint guidance).
* Static regex / URL `.parse()` — `.expect("CRATE: <pattern>
  is valid")` for compile-time-known patterns.
* Slice `.try_into()` to `[u8; N]` with bounds-checked indices —
  `.expect("CRATE: N-byte slice converts to [u8; N] (bounds checked
  above)")`.
* Guard-protected `Option::unwrap` — `.expect("CRATE: <invariant
  enforced upstream>")` with a searchable, actionable message.
* `find_firmware().parent().unwrap()` — `.expect(...)` (a discovered
  firmware path always has a parent dir).
* `fbuild_build::avr::mcu_config::get_avr_config().unwrap()` in
  `handlers/operations/deploy.rs` — converted to `?` since the
  enclosing pipeline closure already returns `Result`.
* `axum::response::Response::builder().body(...).unwrap()` —
  `.expect("static NDJSON response builder cannot fail")`.

Files touched:

  crates/fbuild-build/src/compiler.rs
  crates/fbuild-build/src/esp32/orchestrator/build.rs
  crates/fbuild-build/src/pipeline/compile.rs
  crates/fbuild-build/src/pipeline/sequential.rs
  crates/fbuild-cli/src/cli/bloat_lookup.rs
  crates/fbuild-cli/src/cli/clang_tools.rs
  crates/fbuild-cli/src/lib_select.rs
  crates/fbuild-cli/src/mcp/server.rs
  crates/fbuild-config/src/bin/enrich_boards.rs
  crates/fbuild-config/src/ini_parser/parser.rs
  crates/fbuild-daemon/src/handlers/operations/build.rs
  crates/fbuild-daemon/src/handlers/operations/deploy.rs
  crates/fbuild-daemon/src/handlers/websockets.rs
  crates/fbuild-daemon/src/main.rs
  crates/fbuild-deploy/src/esp32/image.rs
  crates/fbuild-header-scan/src/scanner.rs
  crates/fbuild-paths/src/lib.rs
  crates/fbuild-python/src/async_serial_monitor.rs
  crates/fbuild-python/src/json_rpc.rs
  crates/fbuild-python/src/serial_monitor.rs
  crates/fbuild-serial/src/crash_decoder.rs

No function signatures changed — blast radius is local to each call
site. Only one site (deploy.rs `avr_config?`) became fallible, and the
enclosing closure already had a `Result` return type so no caller
change was needed.

## Lint extension + rename

Renamed `dylints/ban_unwrap_in_daemon_handlers/` →
`dylints/ban_unwrap_in_production/`, since the scope is no longer
daemon-handler-only.

Widened the in-scope filter from
`crates/fbuild-daemon/src/handlers/` to:

* `crates/fbuild-daemon/src/**` (all daemon code, not just handlers)
* `crates/fbuild-cli/src/cli/**` (all CLI subcommand code)

Tightened sibling-test-file detection from a substring match
(`name.contains("tests")`, which would have exempted production files
named e.g. `run_tests_pipeline.rs`) to a strict pattern: `tests.rs`,
`*_tests.rs`, or `tests_*.rs`. Existing test-file exemptions still
fire correctly.

Also updated:

* workspace root `Cargo.toml` `[workspace] exclude` entry
* `ci/hooks/crate_guard.py` `APPROVED_CRATE_DIRS` allowlist
* `dylints/README.md` lint catalog
* `dylints/ban_std_sync_mutex_in_async/src/lib.rs` doc reference

## Verification

* `soldr cargo check --workspace --all-targets` — clean
* `soldr cargo clippy --workspace --all-targets -- -D warnings` — clean
  (exit 0; only the pre-existing clippy.toml MSRV-mismatch info note)
* `soldr cargo test --workspace --no-fail-fast --lib` — all crates I
  touched pass; the 3 daemon-lib failures
  (`handlers::{health,locks}::tests::...`) reproduce identically on
  origin/main and are unrelated to this change (they panic with "can
  call blocking only when running on the multi-threaded runtime" in
  `status_manager.rs:221` — a pre-existing test-runtime setup bug).

Post-sweep audit script confirms zero production unwrap sites remain
outside `#[cfg(test)]`-annotated functions and sibling test files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zackees
zackees merged commit c52221e into main Jun 29, 2026
82 of 101 checks passed
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2bd06ee6-c93f-4a18-bdfe-92e4c8812f7e

📥 Commits

Reviewing files that changed from the base of the PR and between 861e8bf and 7d25a88.

📒 Files selected for processing (31)
  • Cargo.toml
  • ci/hooks/crate_guard.py
  • crates/fbuild-build/src/compiler.rs
  • crates/fbuild-build/src/esp32/orchestrator/build.rs
  • crates/fbuild-build/src/pipeline/compile.rs
  • crates/fbuild-build/src/pipeline/sequential.rs
  • crates/fbuild-cli/src/cli/bloat_lookup.rs
  • crates/fbuild-cli/src/cli/clang_tools.rs
  • crates/fbuild-cli/src/lib_select.rs
  • crates/fbuild-cli/src/mcp/server.rs
  • crates/fbuild-config/src/bin/enrich_boards.rs
  • crates/fbuild-config/src/ini_parser/parser.rs
  • crates/fbuild-daemon/src/handlers/operations/build.rs
  • crates/fbuild-daemon/src/handlers/operations/deploy.rs
  • crates/fbuild-daemon/src/handlers/websockets.rs
  • crates/fbuild-daemon/src/main.rs
  • crates/fbuild-deploy/src/esp32/image.rs
  • crates/fbuild-header-scan/src/scanner.rs
  • crates/fbuild-paths/src/lib.rs
  • crates/fbuild-python/src/async_serial_monitor.rs
  • crates/fbuild-python/src/json_rpc.rs
  • crates/fbuild-python/src/serial_monitor.rs
  • crates/fbuild-serial/src/crash_decoder.rs
  • dylints/README.md
  • dylints/ban_std_sync_mutex_in_async/src/lib.rs
  • dylints/ban_unwrap_in_daemon_handlers/README.md
  • dylints/ban_unwrap_in_production/Cargo.toml
  • dylints/ban_unwrap_in_production/README.md
  • dylints/ban_unwrap_in_production/rust-toolchain.toml
  • dylints/ban_unwrap_in_production/src/allowlist.txt
  • dylints/ban_unwrap_in_production/src/lib.rs

📝 Walkthrough

Walkthrough

The ban_unwrap_in_daemon_handlers custom Dylint is renamed to ban_unwrap_in_production and its scope is widened to cover both crates/fbuild-daemon/src/ and crates/fbuild-cli/src/cli/. Test-file exemption logic is tightened to filename-pattern matching. All flagged unwrap call sites across the workspace are remediated with expect(...) or poison-tolerant unwrap_or_else.

Changes

ban_unwrap_in_production lint expansion and call-site remediation

Layer / File(s) Summary
Dylint rename, scope expansion, and test-file exemption logic
dylints/ban_unwrap_in_production/Cargo.toml, dylints/ban_unwrap_in_production/README.md, dylints/ban_unwrap_in_production/src/lib.rs, dylints/README.md, dylints/ban_std_sync_mutex_in_async/src/lib.rs
Renames crate to ban_unwrap_in_production, expands IN_SCOPE_PREFIXES to daemon + CLI/cli, rewrites is_test_file with explicit filename-pattern rules, updates lint message and unit tests, and updates the catalog README and mirror-lint comment.
Workspace config and CI guard updates
Cargo.toml, ci/hooks/crate_guard.py
Updates the workspace exclude list and APPROVED_CRATE_DIRS allowlist to reference ban_unwrap_in_production.
Daemon handler unwrap fixes
crates/fbuild-daemon/src/handlers/operations/build.rs, crates/fbuild-daemon/src/handlers/operations/deploy.rs, crates/fbuild-daemon/src/handlers/websockets.rs, crates/fbuild-daemon/src/main.rs
Replaces unwrap() with expect(...) in build/websocket/CORS handlers; changes AVR deploy config retrieval to ? error propagation.
CLI unwrap fixes
crates/fbuild-cli/src/cli/bloat_lookup.rs, crates/fbuild-cli/src/cli/clang_tools.rs, crates/fbuild-cli/src/lib_select.rs, crates/fbuild-cli/src/mcp/server.rs
Replaces unwrap() with expect(...) for semaphore acquisition, query string derivation, JSON serialization, and MCP request ID extraction.
Build pipeline poison-tolerant mutex fixes
crates/fbuild-build/src/compiler.rs, crates/fbuild-build/src/esp32/orchestrator/build.rs, crates/fbuild-build/src/pipeline/compile.rs, crates/fbuild-build/src/pipeline/sequential.rs
Replaces lock().unwrap() / into_inner().unwrap() with `unwrap_or_else(
Python serial monitor poison-tolerant mutex and expect fixes
crates/fbuild-python/src/serial_monitor.rs, crates/fbuild-python/src/async_serial_monitor.rs, crates/fbuild-python/src/json_rpc.rs
Replaces unwrap() with `unwrap_or_else(
Remaining crate unwrap-to-expect fixes
crates/fbuild-config/src/bin/enrich_boards.rs, crates/fbuild-config/src/ini_parser/parser.rs, crates/fbuild-deploy/src/esp32/image.rs, crates/fbuild-header-scan/src/scanner.rs, crates/fbuild-paths/src/lib.rs, crates/fbuild-serial/src/crash_decoder.rs
Replaces bare unwrap() with descriptive expect(...) calls across board enrichment, INI parser, ESP32 image parsing, header scanner, path utilities, and crash decoder regex initialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • FastLED/fbuild#849: Modifies the same ci/hooks/crate_guard.py APPROVED_CRATE_DIRS allowlist for dylints/* directories.

Poem

🐇 Hoppity hop through the code I go,
No more unwrap() lurking below!
Each panic now has a message clear,
And poisoned mutexes hold no fear.
From daemon to CLI, production's tight—
The lint shall guard us through the night! ✨

✨ 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/unwrap-prod-sweep-844

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 added a commit that referenced this pull request Jun 29, 2026
Releases the post-#813 / #802 / #826 / #844 work landed across:
- PR #843 — workspace-wide timeout sweep (#802 family)
- PR #837, #849, #850, #851 — internal-bridges + dylint sweep (#826 + #844)
- PR #842 — async migration follow-ups (#813)

15 dylints now ship in dylints/ (4 from earlier, 10 from #826/#844,
plus the renamed ban_unwrap_in_production). 7 internal bridge APIs:
fbuild_core::{http, fs, time, channel, path::canonicalize_existing},
fbuild_paths::temp_subdir, fbuild_cli::output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fastled-project-sync fastled-project-sync Bot moved this to Triage in FastLED Tracker Jun 30, 2026
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.

internal bridge APIs + dylint bans (total sweep, no grandfathering)

1 participant