Help us improve
Share bugs, ideas, or general feedback.
From beagle-rust
Verifies code review findings using a multi-step protocol to reduce false positives. Includes Rust-specific checks for edition, dead code, and trait impls.
npx claudepluginhub existential-birds/beagle --plugin beagle-rustHow this skill is triggered — by the user, by Claude, or both
Slash command
/beagle-rust:review-verification-protocolThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This protocol MUST be followed before reporting any code review finding. Skipping these steps leads to false positives that waste developer time and erode trust in reviews.
Reviews Rust code for ownership, borrowing, lifetimes, error handling, trait design, unsafe usage, and common mistakes. Covers Rust 2024 edition patterns and modern idioms.
Enforces pre-report verification checklists for code review findings like unused code, missing validation, type assertions, and leaks to reduce false positives.
Enforces a multi-step verification protocol for code review findings to eliminate false positives. Must be loaded before reporting any review issues.
Share bugs, ideas, or general feedback.
This protocol MUST be followed before reporting any code review finding. Skipping these steps leads to false positives that waste developer time and erode trust in reviews.
Complete these in order before you add a finding. Skip a gate only when it clearly does not apply (e.g. skip the usages gate if the finding is not about dead code or “unused”).
impl, or macro_rules! block you read in full (not only a diff hunk or partial snippet).rg, IDE references, or equivalent) and either state zero matches for the symbol you call unused, or list each match and why it still supports the finding.#[cfg], or error propagation that could make the pattern intentional; note one concrete checked location (path + rough location) or state “none relevant after search.”Cargo.toml for the crate under review and either quote the [package] edition = "..." line or state the default edition applies and name the manifest path you checked.Before flagging ANY issue, verify:
Before flagging, you MUST:
pub and used by other crates in the workspace#[cfg])Common false positives:
#[cfg(test)] items only used in test buildsFrom/Into conversionsBefore flagging, you MUST:
?)unwrap() isn't in test code or after a safety-ensuring checkCommon false positives:
unwrap() in tests and examples (expected pattern)expect("reason") after validation (e.g., regex::Regex::new on a literal)? (the caller handles it)let _ = tx.send(...) — intentional when receiver may have droppedBefore flagging, you MUST:
Cargo.toml-> impl Trait captures ALL in-scope lifetimes by default+ use<'a> syntax, this is precise capture control, not a mistakeCommon false positives:
impl Trait — edition 2024 captures them implicitly+ use<'a, T> syntax — this is the new precise capturing syntax, not an errorBefore flagging, you MUST:
unsafe fnunsafe_op_in_unsafe_fn is deny-by-default — unsafe operations inside unsafe fn REQUIRE explicit unsafe {} blocksCommon false positives:
unsafe {} blocks inside unsafe fn — REQUIRED in edition 2024, not redundantunsafe extern "C" {} — REQUIRED in edition 2024, not optional#[unsafe(no_mangle)] / #[unsafe(export_name)] — REQUIRED in edition 2024Before flagging, you MUST:
Copy (clone on Copy types is a no-op)Common false positives:
Arc::clone(&arc) — this is the recommended explicit clone for Arctokio::spawn — required for 'static boundBefore flagging, you MUST:
Mutex, RwLock, or atomic operations protect the accessArc<Mutex<T>>)Common false positives:
Arc<Mutex<T>> — already thread-safestd::sync::atomic operations — designed for concurrent accessBefore flagging, you MUST:
Do NOT flag:
.collect() on small iteratorsONLY use for:
unsafe code with unsound invariantsUse for:
Use for:
String parameters where &str would work#[must_use] on functions with important return valuesUse for:
#[non_exhaustive]These are NOT review blockers.
if let vs match for single variant)| Pattern | Why It's Valid |
|---|---|
unwrap() in tests | Standard test behavior — panics on unexpected errors |
.clone() in test setup | Clarity over performance |
use super::* in test modules | Standard pattern for accessing parent items |
Box<dyn Error> in binaries | Not every app needs custom error types |
String fields in structs | Owned data is correct for struct fields |
Arc::clone(&x) | Explicit Arc cloning is idiomatic and recommended |
#[allow(clippy::...)] with reason | Intentional suppression is valid |
#[expect(lint)] instead of #[allow] | Self-cleaning suppression (stable since 1.81) — warns when lint no longer triggers |
unsafe {} inside unsafe fn | Required in edition 2024 (unsafe_op_in_unsafe_fn = deny) |
unsafe extern "C" {} | Required in edition 2024 for extern blocks |
#[unsafe(no_mangle)] | Required in edition 2024 for safety-relevant attributes |
#[unsafe(export_name = "...")] | Required in edition 2024 for safety-relevant attributes |
+ use<'a, T> on impl Trait returns | Precise capture syntax for edition 2024 RPIT |
r#gen as identifier | gen is reserved in edition 2024 |
LazyLock / LazyCell | Standard library replacements for once_cell/lazy_static (stable since 1.80) |
async fn in trait definitions | No longer needs async-trait crate (stable since 1.75) |
#[diagnostic::on_unimplemented] | Custom trait error messages (stable since 1.78) |
| Pattern | Why It's Valid |
|---|---|
std::sync::Mutex for short critical sections | Tokio docs recommend this for non-async locks |
tokio::spawn without join | Valid for background tasks with shutdown signaling |
select! with default branch | Non-blocking check, intentional pattern |
#[tokio::test] without multi_thread | Default single-thread is fine for most tests |
| Pattern | Why It's Valid |
|---|---|
expect() in tests | Acceptable for test setup/assertions |
#[should_panic] with expected | Valid for testing panic behavior |
| Large test functions | Integration tests can be long |
let _ = ... in test cleanup | Cleanup errors are often unactionable |
| Pattern | Why It's Valid |
|---|---|
todo!() in new code | Valid placeholder during development |
#[allow(dead_code)] during development | Common during iteration |
Multiple impl blocks for one type | Organized by trait or concern |
| Type aliases for complex types | Reduces boilerplate, improves readability |
Flag unnecessary .clone() ONLY IF:
Send/'static boundsCopyFlag missing error context ONLY IF:
? loses meaningful information about what operation failedFlag unsafe ONLY IF:
Edition 2024 unsafe changes — check Cargo.toml edition before flagging:
unsafe {} inside unsafe fn is required (not style) in edition 2024unsafe extern "C" {} is required in edition 2024 — bare extern "C" {} is a compile error#[unsafe(no_mangle)] and #[unsafe(export_name)] are required in edition 2024BEFORE flagging any edition-specific pattern, check Cargo.toml for the project's edition:
[package]
edition = "2024" # or "2021", "2018"
Edition 2024 changes that affect review findings:
| Change | Edition 2021 | Edition 2024 |
|---|---|---|
unsafe inside unsafe fn | Optional style | Required (unsafe_op_in_unsafe_fn = deny) |
extern "C" {} | Valid | Must be unsafe extern "C" {} |
#[no_mangle] | Valid | Must be #[unsafe(no_mangle)] |
#[export_name] | Valid | Must be #[unsafe(export_name)] |
-> impl Trait lifetime capture | Explicit only | Captures all in-scope lifetimes |
gen as identifier | Valid | Reserved keyword (use r#gen) |
! type fallback | Falls back to () | Falls back to ! |
if let temporaries | Dropped at end of block | Dropped earlier (end of if let) |
| Tail expression temporaries | Dropped after locals | Dropped before local variables |
Box<[T]> iteration | Needs explicit .iter() | Has IntoIterator impl |
If edition is not specified, Rust defaults to edition 2015. Most modern projects use 2021 or later.
Cross-reference: The beagle-rust:rust-code-review and beagle-rust:rust-best-practices skills provide edition-specific code review guidance and idiomatic patterns.
Before flagging, you MUST:
macro_rules!$crate is used correctly for exported macros (not crate or self)::core:: / ::alloc:: paths are needed (only for macros used in no_std contexts)#[macro_export]Common false positives:
$crate not used in macros that are only pub(crate) — $crate is for cross-crate usage::std:: in macros for std-only crates — only flag if crate supports no_stdBefore flagging, you MUST:
Cargo.toml for proc-macro = true)syn features are minimized (full syn with "full" feature vs selective features)Before flagging, you MUST:
:tt is intentionally used for flexibility (common in TT munching patterns):expr greediness issues actually manifest (test with the macro's actual call sites)Before flagging, you MUST:
#[repr(transparent)] wrapper insteadCommon false positives:
repr(C)repr(transparent) newtype wrappers — the wrapper handles layout*mut c_void) — no layout guarantee neededBefore flagging, you MUST:
Box::into_raw/Box::from_raw pairs)Common false positives:
extern "C" fn callbacks that never panic — catch_unwind not needed*const c_char from CStr::as_ptr() held within the same scope — lifetime is fineunsafe — bindgen output is inherently unsafe-heavy by designBefore flagging, you MUST:
Relaxed is sufficient (counters, flags with no dependent data)Acquire/Release vs SeqCst choice matters (most code doesn't need SeqCst)Common false positives:
Relaxed on simple counters/metrics — no ordering needed for independent valuesRelaxed on boolean flags polled in a loop — the loop provides eventual visibilitySeqCst used "for safety" — not wrong, just potentially over-synchronizedSubmission gate — Pass: Every finding uses [FILE:LINE] ISSUE_TITLE and includes the exact line (or minimal contiguous lines) that demonstrates the issue, so a reader can jump to the proof without trusting memory.
Final verification:
[FILE:LINE] ISSUE_TITLEIf uncertain about any finding, either: