Skip to content

fix(core): remove three runtime expect()/panic sites#48

Merged
pofallon merged 1 commit into
mainfrom
fix-runtime-panics
May 26, 2026
Merged

fix(core): remove three runtime expect()/panic sites#48
pofallon merged 1 commit into
mainfrom
fix-runtime-panics

Conversation

@pofallon
Copy link
Copy Markdown
Contributor

Audit flagged three production paths where .expect(...) could panic if a "should be impossible" invariant got violated. Exemplary CLI code should not panic in runtime paths.

Closes gap #7 from the post-1.0 audit.

Changes

crates/core/src/state.rs:226-229 — removed impl Default for StateManager:

  • The Default impl called Self::new().expect("Failed to create default StateManager").
  • StateManager::new() is fallible (derives a cache directory + does IO) — a Default impl that hides the Result is wrong-shaped.
  • Audit confirmed no production callers use StateManager::default(); every site uses new()? / new_with_cache_dir(path)? and propagates.
  • Replaced with a NOTE comment so a future contributor doesn't accidentally re-add it.

crates/core/src/config.rs:1536-1540named_configs single-match path:

  • The match arm gates named_configs.len() == 1, so next() is guaranteed Some — but the .expect(...) was guarding it anyway.
  • Replaced with a pattern match that returns a ConfigError::Validation on the impossible None case. Defense-in-depth.

crates/core/src/retry.rs:206-213 — restructured retry loop:

  • Returns the error DIRECTLY on the last attempt instead of stashing in last_error: Option<E> and unwrapping after the loop.
  • Removes .expect("Should have at least one error").
  • Adds debug_assert!(false, ...) + unreachable!(...) as defense — 0..=max_attempts (u32) always runs at least once.

Verification

  • cargo build
  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test --lib → 280 deacon + 1081 deacon-core pass (no regression)
  • CI green

🤖 Generated with Claude Code

Audit flagged three production paths where `.expect(...)` could panic
if a "should be impossible" invariant got violated. Exemplary CLI code
should not panic in runtime paths — replace each with an explicit
fallback or restructure so the impossible case is unrepresentable.

## Changes

`crates/core/src/state.rs:226-229`:
- Removed `impl Default for StateManager` (which called
  `Self::new().expect("Failed to create default StateManager")`).
- `StateManager::new()` is fallible — derives a cache directory + does
  IO — so a Default impl that hides the Result is wrong-shaped.
- Audit confirmed NO production callers use `StateManager::default()`;
  every site uses `StateManager::new()?` or
  `StateManager::new_with_cache_dir(path)?` and propagates the Result.
- Replaced the impl with a NOTE comment so a future contributor doesn't
  accidentally re-add it.

`crates/core/src/config.rs:1536-1540`:
- The match arm gates `named_configs.len() == 1`, so `next()` is
  guaranteed `Some` — but the `.expect(...)` was guarding it anyway.
- Replaced with a pattern match that returns a `ConfigError::Validation`
  on the impossible `None` case. Defense-in-depth against a future
  contributor changing how `named_configs` is built.

`crates/core/src/retry.rs:206-213`:
- Restructured the retry loop to return the error DIRECTLY on the last
  attempt, instead of stashing it in `last_error: Option<E>` and
  unwrapping after the loop.
- Removes the `.expect("Should have at least one error")` post-loop.
- Adds a `debug_assert!(false, ...) + unreachable!(...)` as defense if
  the control flow ever changes — `u32::MAX + 1` iterations of the
  `0..=max_attempts` loop must run at least once.

## Verification

- `cargo build`
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets -- -D warnings`
- `cargo test --lib` → 280 deacon + 1081 deacon-core pass (no regression)

Refs: gap #7 from the post-1.0 audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pofallon pofallon merged commit 25c01d1 into main May 26, 2026
8 checks passed
@pofallon pofallon deleted the fix-runtime-panics branch May 26, 2026 00:05
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