diff --git a/src/cli/work.rs b/src/cli/work.rs index 28e5bf03..5e3925e5 100644 --- a/src/cli/work.rs +++ b/src/cli/work.rs @@ -466,9 +466,23 @@ pub(crate) fn execute_work(args: WorkArgs, log: &mut dyn std::io::Write) -> Work serde_json::from_str(&claim_resp).unwrap_or(serde_json::Value::Null); let status = claim["status"].as_str().unwrap_or("unknown"); if status != "acquired" { - let owner = claim["owner"].as_str().unwrap_or("?"); - let age = claim["age_secs"].as_i64().unwrap_or(0); - crate::ui::error(&format!("item held by {owner} ({age}s) — cannot claim")); + // "held" (a live claim by another owner) and "blocked" (an unaccepted + // handoff — see `ClaimOutcome::BlockedByAssignee`) are different + // shapes: "blocked" has no `owner`/`age_secs` at all, so formatting + // it as if it were "held" printed the nonsensical "held by ? (0s)" + // instead of the actionable reason the claim response already carries. + let msg = match status { + "blocked" => claim["reason"] + .as_str() + .unwrap_or("item is blocked by an unaccepted handoff") + .to_string(), + _ => { + let owner = claim["owner"].as_str().unwrap_or("?"); + let age = claim["age_secs"].as_i64().unwrap_or(0); + format!("item held by {owner} ({age}s) — cannot claim") + } + }; + crate::ui::error(&msg); return 1.into(); } let item_id = claim["item_id"] diff --git a/src/github/bridge/tick.rs b/src/github/bridge/tick.rs index b07c517a..bfbd997b 100644 --- a/src/github/bridge/tick.rs +++ b/src/github/bridge/tick.rs @@ -388,6 +388,19 @@ fn record_claim( }) .map(|l| l.id) }); + // This decision is made once, right here, not re-evaluated on + // later ticks for this same issue (see the `Some(existing)` arm + // above) -- so a `work_agent` configured after this point never + // retroactively dispatches it. Silence here reads as "being + // worked" when it's actually just claimed and stuck; say so. + if ctx.config.work_agent.is_none() { + eprintln!( + "github bridge: claimed #{} with no work_agent configured — it will \ + not be auto-dispatched; set AGENTFLARE_BRIDGE_WORK_AGENT or dispatch \ + it manually (`agentflare work`)", + issue.number + ); + } // `handoff`'s `recipient="github"` path embeds the full // structured payload (content/completed/remaining/thread_id) as // a hidden marker after the human-readable body -- recover it