From 1356a1f04905689929db1f5f8f957a97bc127d19 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 25 Jul 2026 20:46:16 +0200 Subject: [PATCH] fix(#2624): make hive-forge failures unmistakable on stderr `fn main() -> Result<()>` let anyhow's Debug impl render failures with a bare `Error:` header. hive-forge is almost always invoked from an agent's bash task, where the completion wake points at the task's .out file - so a failure that only writes to .err is easy to miss entirely (mara's "had no clue it failed" on #2624). Wrap the dispatch in a run() and own the failure path in main(): - prefix with the binary name (`hive-forge: FAILED: ...`) so the line is unmistakably ours in a mixed transcript, - render with {:#} (alternate Display), which keeps the full context chain inline - plain Display would have dropped every `.context()` below the top one, - return ExitCode::FAILURE explicitly rather than relying on the Termination impl. Half of #2624: the other half (surfacing .err in the bash-mcp completion when a task exits non-zero) is damocles's, per the issue thread. --- hive-forge/src/main.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/hive-forge/src/main.rs b/hive-forge/src/main.rs index 3aad7deb..3f9960f3 100644 --- a/hive-forge/src/main.rs +++ b/hive-forge/src/main.rs @@ -28,6 +28,7 @@ mod verbs; use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; +use std::process::ExitCode; #[derive(Parser)] #[command( @@ -173,7 +174,24 @@ enum Verb { CiRerun(verbs::ci_rerun::Args), } -fn main() -> Result<()> { +/// Wrapper over [`run`] that owns how a failure reaches the operator. +/// +/// `fn main() -> Result<()>` would format the error with anyhow's `Debug` +/// impl, which prints a bare `Error:` header. `hive-forge` is almost always +/// invoked from an agent's bash task, where stdout is what gets surfaced +/// first and stderr is easy to miss — so failures are prefixed with the +/// binary name to be unmistakably ours, and rendered with `{:#}` +/// (alternate `Display`), which keeps the full `context` chain inline +/// rather than dropping it the way plain `Display` would. +fn main() -> ExitCode { + if let Err(e) = run() { + eprintln!("hive-forge: FAILED: {e:#}"); + return ExitCode::FAILURE; + } + ExitCode::SUCCESS +} + +fn run() -> Result<()> { let cli = Cli::parse(); let client = client::Client::from_env(cli.repo, cli.json, cli.forge) .context("initialize forge client")?;