From f9f1346eae3272a353e0052683ebd150dddcfc79 Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 18 May 2026 22:09:34 +0200 Subject: [PATCH] clippy: zero pedantic warnings across the tree --- hive-ag3nt/src/bin/hive-ag3nt.rs | 12 +++++++----- hive-ag3nt/src/bin/hive-m1nd.rs | 9 +++++---- hive-ag3nt/src/events.rs | 12 ++++++------ hive-ag3nt/src/mcp.rs | 6 ++++-- hive-ag3nt/src/plugins.rs | 10 ++++------ hive-ag3nt/src/turn.rs | 9 +++++---- hive-ag3nt/src/turn_stats.rs | 8 ++++---- hive-ag3nt/src/web_ui.rs | 7 ++----- hive-c0re/src/actions.rs | 4 ++-- hive-c0re/src/agent_server.rs | 6 +++++- hive-c0re/src/approvals.rs | 3 +++ hive-c0re/src/auto_update.rs | 4 ++-- hive-c0re/src/broker.rs | 2 +- hive-c0re/src/coordinator.rs | 4 +++- hive-c0re/src/dashboard.rs | 15 +++++++-------- hive-c0re/src/dashboard_events.rs | 8 ++++---- hive-c0re/src/forge.rs | 2 +- hive-c0re/src/manager_server.rs | 4 ++-- hive-c0re/src/migrate.rs | 4 ++-- hive-c0re/src/operator_questions.rs | 3 ++- 20 files changed, 71 insertions(+), 61 deletions(-) diff --git a/hive-ag3nt/src/bin/hive-ag3nt.rs b/hive-ag3nt/src/bin/hive-ag3nt.rs index 927b48d..84dfeac 100644 --- a/hive-ag3nt/src/bin/hive-ag3nt.rs +++ b/hive-ag3nt/src/bin/hive-ag3nt.rs @@ -148,6 +148,7 @@ async fn main() -> Result<()> { } } +#[allow(clippy::too_many_arguments, clippy::similar_names)] async fn serve( socket: &Path, interval: Duration, @@ -245,13 +246,13 @@ async fn serve( s.record(&row); } - // After turn completes, check if there are pending messages waiting. - // If so, immediately process them instead of blocking on recv(). - // This ensures messages queued during the turn are processed ASAP. + // After turn completes, log whether messages arrived during + // the turn — the outer loop will iterate back to recv() on + // its own (the Empty-arm sleep only fires when recv + // actually returned Empty), so no explicit continue needed. let pending = inbox_unread(socket).await; if pending > 0 { tracing::info!(%pending, "pending messages after turn; fetching next"); - continue; // Loop back to recv() immediately instead of sleeping } } Ok(AgentResponse::Empty) => { @@ -409,10 +410,11 @@ async fn fetch_agent_post_turn_counts(socket: &Path) -> (Option, Option 0 { tracing::info!(%pending, "pending messages after turn; fetching next"); - continue; } } Ok(ManagerResponse::Empty) => { @@ -333,8 +333,9 @@ async fn fetch_manager_post_turn_counts(socket: &Path) -> (Option, Option Option { + fn from_usage_obj(u: &serde_json::Value) -> Self { let field = |k: &str| u.get(k).and_then(serde_json::Value::as_u64).unwrap_or(0); - Some(Self { + Self { input_tokens: field("input_tokens"), output_tokens: field("output_tokens"), cache_read_input_tokens: field("cache_read_input_tokens"), cache_creation_input_tokens: field("cache_creation_input_tokens"), - }) + } } } @@ -494,7 +494,7 @@ impl Bus { }); } - /// Broadcast a status flip (online / needs_login_*). Called by + /// Broadcast a status flip (online / `needs_login_*`). Called by /// the bin entry points + `turn::wait_for_login` + the /// `post_login_*` handlers — every site that mutates the /// `Arc>` should also call this so the web UI diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 7e6bc0a..d6c4ea2 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -308,11 +308,13 @@ where /// content failure and the model would burn a turn retrying it. pub fn annotate_retries(mut s: String, retries: u32) -> String { if retries > 0 { + use std::fmt::Write as _; let suffix = if retries == 1 { "retry" } else { "retries" }; - s.push_str(&format!( + let _ = write!( + s, "\n\n(note: hive socket connect needed {retries} {suffix} — c0re likely \ restarted. Your request did succeed on the final attempt; no action needed.)" - )); + ); } s } diff --git a/hive-ag3nt/src/plugins.rs b/hive-ag3nt/src/plugins.rs index baea363..0fb0b98 100644 --- a/hive-ag3nt/src/plugins.rs +++ b/hive-ag3nt/src/plugins.rs @@ -27,9 +27,8 @@ const AUTO_UPDATE_PATH: &str = "/etc/hyperhive/claude-plugins-auto-update.json"; /// "already exists" message and exits non-zero on some versions). /// Required before any `@` install can resolve. async fn add_marketplaces() { - let raw = match tokio::fs::read_to_string(MARKETPLACES_PATH).await { - Ok(s) => s, - Err(_) => return, + let Ok(raw) = tokio::fs::read_to_string(MARKETPLACES_PATH).await else { + return; }; let sources: Vec = match serde_json::from_str(&raw) { Ok(v) => v, @@ -107,9 +106,8 @@ async fn update_marketplaces() { /// journald. The manager itself passes `None` — there's nobody above /// it to notify. pub async fn install_configured(socket: &Path, notify_recipient: Option<&str>) { - let raw = match tokio::fs::read_to_string(PLUGINS_PATH).await { - Ok(s) => s, - Err(_) => return, + let Ok(raw) = tokio::fs::read_to_string(PLUGINS_PATH).await else { + return; }; let specs: Vec = match serde_json::from_str(&raw) { Ok(v) => v, diff --git a/hive-ag3nt/src/turn.rs b/hive-ag3nt/src/turn.rs index b4ad794..99ca80e 100644 --- a/hive-ag3nt/src/turn.rs +++ b/hive-ag3nt/src/turn.rs @@ -222,7 +222,12 @@ pub async fn compact_session(files: &TurnFiles, bus: &Bus) -> Result<()> { Ok(()) } +#[allow(clippy::too_many_lines)] async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result { + // Keep the last STDERR_TAIL_LINES of stderr so a non-zero exit can + // include real context in the bail message (and downstream in the + // failure notification to the manager) instead of just "exit 1". + const STDERR_TAIL_LINES: usize = 20; let model = bus.model(); let resume = !bus.take_skip_continue(); if !resume { @@ -311,10 +316,6 @@ async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result } } }); - // Keep the last STDERR_TAIL_LINES of stderr so a non-zero exit can - // include real context in the bail message (and downstream in the - // failure notification to the manager) instead of just "exit 1". - const STDERR_TAIL_LINES: usize = 20; let stderr_tail: Arc>> = Arc::new(Mutex::new(VecDeque::with_capacity(STDERR_TAIL_LINES))); let tail_clone = stderr_tail.clone(); diff --git a/hive-ag3nt/src/turn_stats.rs b/hive-ag3nt/src/turn_stats.rs index 899a6e6..ffae280 100644 --- a/hive-ag3nt/src/turn_stats.rs +++ b/hive-ag3nt/src/turn_stats.rs @@ -1,8 +1,8 @@ //! Per-turn analytics sink. One sqlite row per claude turn captures: -//! identity (model, wake_from, result_kind), timing (started_at, -//! ended_at, duration_ms), cost (token counts), behaviour (tool-call +//! identity (`model`, `wake_from`, `result_kind`), timing (`started_at`, +//! `ended_at`, `duration_ms`), cost (token counts), behaviour (tool-call //! count + per-tool breakdown), and post-turn snapshot metrics -//! (open_threads_count, open_reminders_count). +//! (`open_threads_count`, `open_reminders_count`). //! //! Lives next to `hyperhive-events.sqlite` in the agent's state dir //! so the host-side state vacuum sweep can reach both. Schema is @@ -65,7 +65,7 @@ const MIGRATIONS: &[&str] = &[ /// One row to be inserted. `Option`-wrapped fields default to NULL /// when the harness couldn't gather them (e.g. socket roundtrip for -/// open_threads failed) so a partial row beats no row. +/// `open_threads` failed) so a partial row beats no row. #[derive(Debug, Clone)] pub struct TurnStatRow { pub started_at: i64, diff --git a/hive-ag3nt/src/web_ui.rs b/hive-ag3nt/src/web_ui.rs index 2d69153..caca25e 100644 --- a/hive-ag3nt/src/web_ui.rs +++ b/hive-ag3nt/src/web_ui.rs @@ -527,11 +527,8 @@ async fn post_compact(State(state): State) -> Response { let lock = state.turn_lock.clone(); // Reject immediately if a normal turn is in flight — concurrent access // to the claude session is unsafe and produces garbled output. - let guard = match lock.try_lock_owned() { - Ok(g) => g, - Err(_) => { - return error_response("turn in flight — wait for it to finish before compacting"); - } + let Ok(guard) = lock.try_lock_owned() else { + return error_response("turn in flight — wait for it to finish before compacting"); }; let bus = state.bus.clone(); let files = state.files.clone(); diff --git a/hive-c0re/src/actions.rs b/hive-c0re/src/actions.rs index edc837e..74c37b2 100644 --- a/hive-c0re/src/actions.rs +++ b/hive-c0re/src/actions.rs @@ -324,7 +324,7 @@ pub async fn destroy(coord: &Arc, name: &str, purge: bool) -> Resul tracing::info!(%name, purge, "destroy"); // Guard auto-clears on the success path's final scope exit and on // every early-return / cancellation along the way. - let _guard = coord.transient_guard(name, TransientKind::Destroying); + let guard = coord.transient_guard(name, TransientKind::Destroying); lifecycle::destroy(name).await?; coord.unregister_agent(name); let runtime = Coordinator::agent_dir(name); @@ -359,7 +359,7 @@ pub async fn destroy(coord: &Arc, name: &str, purge: bool) -> Resul "agent destroyed" }, ); - drop(_guard); + drop(guard); coord.notify_manager(&HelperEvent::Destroyed { agent: name.to_owned(), }); diff --git a/hive-c0re/src/agent_server.rs b/hive-c0re/src/agent_server.rs index f4c0148..5da185a 100644 --- a/hive-c0re/src/agent_server.rs +++ b/hive-c0re/src/agent_server.rs @@ -376,7 +376,11 @@ mod tests { fn auto_reminder_path_format() { let p = auto_reminder_path("damocles"); assert!(p.starts_with("/agents/damocles/state/reminders/auto-")); - assert!(p.ends_with(".md")); + assert!( + std::path::Path::new(&p) + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("md")) + ); } #[test] diff --git a/hive-c0re/src/approvals.rs b/hive-c0re/src/approvals.rs index 2e9dad9..2deddf5 100644 --- a/hive-c0re/src/approvals.rs +++ b/hive-c0re/src/approvals.rs @@ -168,8 +168,11 @@ impl Approvals { /// Mark pending -> approved (or fail if not pending). Returns the (now-updated) /// approval so the caller can run the action and pass the agent name. + #[allow(clippy::type_complexity)] pub fn mark_approved(&self, id: i64) -> Result { let conn = self.conn.lock().unwrap(); + // Row shape: (agent, kind, commit_ref, requested_at, status, + // fetched_sha, description). let current: Option<( String, String, diff --git a/hive-c0re/src/auto_update.rs b/hive-c0re/src/auto_update.rs index 36dc72c..9a18bd7 100644 --- a/hive-c0re/src/auto_update.rs +++ b/hive-c0re/src/auto_update.rs @@ -63,7 +63,7 @@ pub async fn rebuild_agent(coord: &Arc, name: &str, current_rev: &s // lifecycle::rebuild. Dashboard rebuilds already do this via // lifecycle_action; this catches the auto-update scan + any // other direct caller. - let _guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); + let guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); let result = lifecycle::rebuild( name, &coord.hyperhive_flake, @@ -75,7 +75,7 @@ pub async fn rebuild_agent(coord: &Arc, name: &str, current_rev: &s &coord.operator_pronouns, ) .await; - drop(_guard); + drop(guard); match &result { Ok(()) => { if let Err(e) = std::fs::write(rev_marker_path(name), current_rev) { diff --git a/hive-c0re/src/broker.rs b/hive-c0re/src/broker.rs index af36e4c..4cf66c3 100644 --- a/hive-c0re/src/broker.rs +++ b/hive-c0re/src/broker.rs @@ -512,7 +512,7 @@ impl Broker { /// Clear the failure state on a pending reminder so the /// scheduler picks it up again. No-op when the row is already - /// fresh (attempt_count == 0). Returns the number of rows + /// fresh (`attempt_count == 0`). Returns the number of rows /// affected so callers can distinguish "retried" from "no /// such pending reminder" (already delivered, or wrong id). pub fn reset_reminder_failure(&self, id: i64) -> Result { diff --git a/hive-c0re/src/coordinator.rs b/hive-c0re/src/coordinator.rs index 1128c6b..11adf9d 100644 --- a/hive-c0re/src/coordinator.rs +++ b/hive-c0re/src/coordinator.rs @@ -215,6 +215,7 @@ impl Coordinator { /// already have an authoritative timestamp from the db update, /// the tiny skew between "row updated" and "event emitted" is /// presentation-only and doesn't matter to clients. + #[allow(clippy::too_many_arguments)] pub fn emit_approval_resolved( &self, id: i64, @@ -247,6 +248,7 @@ impl Coordinator { /// both operator-targeted (`target = None`) and peer-to-peer /// (`target = Some(agent)`) threads — the dashboard surfaces /// both, distinguishing visually + offering operator override. + #[allow(clippy::too_many_arguments)] pub fn emit_question_added( &self, id: i64, @@ -318,7 +320,7 @@ impl Coordinator { /// resolves to — lifecycle ops, destroy, approve (post-spawn), /// rebuild, meta-update, and the crash-watcher's periodic poll. /// Cheap when nothing changed (one `nixos-container list` + a - /// HashMap diff + zero emits). + /// `HashMap` diff + zero emits). pub async fn rescan_containers_and_emit(self: &Arc) { let fresh = container_view::build_all(self).await; let mut last = self.last_containers.lock().await; diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 2790cbd..c5efe5b 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -187,7 +187,7 @@ struct StateSnapshot { meta_inputs: Vec, } -/// OpQuestion + computed `question_refs` / `answer_refs`. Built +/// `OpQuestion` + computed `question_refs` / `answer_refs`. Built /// from the snapshot read; the live channel attaches the same /// fields directly on `QuestionAdded` / `QuestionResolved`. #[derive(Serialize)] @@ -1207,7 +1207,7 @@ pub(crate) fn emit_meta_inputs_snapshot(coord: &Coordinator) { /// natural-language text but aren't part of the path itself. Any /// token starting with `/agents/`, `/shared/`, or /// `/var/lib/hyperhive/{agents,shared}/` is a candidate. The -/// allow-list + is_file check happens via the same +/// allow-list + `is_file` check happens via the same /// `resolve_state_path` helper the read endpoint uses, so the /// security rules can't drift. pub(crate) fn scan_validated_paths(body: &str) -> Vec { @@ -1222,7 +1222,7 @@ pub(crate) fn scan_validated_paths(body: &str) -> Vec { // Trim trailing natural-language punctuation that wouldn't // be part of any real path. Inline rather than via a regex // dep — the set is small and the call is hot. - let token = raw.trim_end_matches(|c: char| matches!(c, ',' | ';' | ':' | ')' | ']' | '}' | '.' | '\'' | '"')); + let token = raw.trim_end_matches([',', ';', ':', ')', ']', '}', '.', '\'', '"']); if token.is_empty() { continue; } @@ -1265,9 +1265,8 @@ async fn get_state_file( let body_bytes = if truncated { &bytes[..MAX_BYTES] } else { &bytes[..] }; let mut body = String::from_utf8_lossy(body_bytes).into_owned(); if truncated { - body.push_str(&format!( - "\n\n--- truncated at {MAX_BYTES} of {size} bytes ---\n" - )); + use std::fmt::Write as _; + let _ = write!(body, "\n\n--- truncated at {MAX_BYTES} of {size} bytes ---\n"); } ([("content-type", "text/plain; charset=utf-8")], body).into_response() } @@ -1575,9 +1574,9 @@ where Fut: std::future::Future>, { let logical = strip_container_prefix(name); - let _guard = state.coord.transient_guard(&logical, kind); + let guard = state.coord.transient_guard(&logical, kind); let result = body(logical.clone()).await; - drop(_guard); + drop(guard); match result { Ok(()) => { extra(state, &logical); diff --git a/hive-c0re/src/dashboard_events.rs b/hive-c0re/src/dashboard_events.rs index fe9f57c..4d5346e 100644 --- a/hive-c0re/src/dashboard_events.rs +++ b/hive-c0re/src/dashboard_events.rs @@ -146,15 +146,15 @@ pub enum DashboardEvent { /// Clients drop the spinner row. TransientCleared { seq: u64, name: String }, /// One container row changed — new container appeared (post-spawn - /// finalise), an existing one flipped running/needs_update/sha, - /// etc. Clients upsert by `container.name`. Payload carries the - /// full row so cold-loaded clients and event-driven clients + /// finalise), an existing one flipped `running` / `needs_update` / + /// `sha`, etc. Clients upsert by `container.name`. Payload carries + /// the full row so cold-loaded clients and event-driven clients /// converge on the same render. /// /// Fired by `Coordinator::rescan_containers_and_emit`, which diffs /// a fresh `nixos-container list`–derived snapshot against the /// last one cached on the coordinator. Mutation sites (lifecycle - /// endpoints, actions::destroy / approve, crash_watch's poll loop) + /// endpoints, `actions::destroy` / approve, `crash_watch`'s poll loop) /// call the rescan after their work lands. ContainerStateChanged { seq: u64, diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index 7018f32..b82a720 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -165,6 +165,7 @@ async fn ensure_user_exists(name: &str, admin: bool) -> Result<()> { /// monotonic clock so re-issuing doesn't collide with an existing /// token of the same name in the DB. async fn mint_and_persist_token(name: &str, path: &Path) -> Result<()> { + use std::os::unix::fs::PermissionsExt; let token_name = format!( "{TOKEN_NAME_PREFIX}-{}", std::time::SystemTime::now() @@ -190,7 +191,6 @@ async fn mint_and_persist_token(name: &str, path: &Path) -> Result<()> { } std::fs::write(path, format!("{token}\n")) .with_context(|| format!("write token to {}", path.display()))?; - use std::os::unix::fs::PermissionsExt; let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)); tracing::info!(%name, path = %path.display(), %token_name, "forge: persisted access token"); Ok(()) diff --git a/hive-c0re/src/manager_server.rs b/hive-c0re/src/manager_server.rs index 99fae61..3cf7d30 100644 --- a/hive-c0re/src/manager_server.rs +++ b/hive-c0re/src/manager_server.rs @@ -234,9 +234,9 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc) -> ManagerResp message: "update: hyperhive_flake has no canonical path".into(), }; }; - let _guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); + let guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); let result = crate::auto_update::rebuild_agent(coord, name, ¤t_rev).await; - drop(_guard); + drop(guard); match result { Ok(()) => { coord.kick_agent(name, "container rebuilt"); diff --git a/hive-c0re/src/migrate.rs b/hive-c0re/src/migrate.rs index 9076aa5..43fe95d 100644 --- a/hive-c0re/src/migrate.rs +++ b/hive-c0re/src/migrate.rs @@ -102,9 +102,9 @@ pub async fn run(coord: &Arc) -> Result<()> { // update activation triggers. Without this, crash_watch // would fire ContainerCrash for every agent here and the // manager would spuriously try to recover them. - let _guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); + let guard = coord.transient_guard(name, crate::coordinator::TransientKind::Rebuilding); let result = repoint_container(name).await; - drop(_guard); + drop(guard); if let Err(e) = result { tracing::warn!(%name, error = ?e, "migration: container repoint failed"); all_ok = false; diff --git a/hive-c0re/src/operator_questions.rs b/hive-c0re/src/operator_questions.rs index ce9a209..5440c11 100644 --- a/hive-c0re/src/operator_questions.rs +++ b/hive-c0re/src/operator_questions.rs @@ -284,7 +284,8 @@ impl OperatorQuestions { ORDER BY answered_at DESC LIMIT ?1", )?; - let rows = stmt.query_map(params![limit as i64], row_to_question)?; + let limit_i = i64::try_from(limit).unwrap_or(i64::MAX); + let rows = stmt.query_map(params![limit_i], row_to_question)?; rows.collect::>>() .map_err(Into::into) }