{"record":{"id":"5d96c64d2f7a1cdb","repo":"Hmbown/CodeWhale","slug":"sub-agent-task-should-finish","errorCode":null,"errorMessage":"sub-agent task should finish","messagePattern":"sub-agent task should finish","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/tests.rs","lineNumber":9016,"sourceCode":"                    return (id, reason);\n                }\n            }\n            tokio::time::sleep(Duration::from_millis(10)).await;\n        }\n    })\n    .await\n    .expect(\"API timeout should publish an Interrupted mailbox lifecycle event\");\n    assert_eq!(interrupted_envelope.0, agent_id);\n    assert!(\n        interrupted_envelope.1.contains(\"API call timed out\"),\n        \"reason should carry the timeout context: {}\",\n        interrupted_envelope.1\n    );\n\n    tokio::time::timeout(Duration::from_secs(5), task_handle)\n        .await\n        .expect(\"sub-agent task must not park waiting for checkpoint input\")\n        .expect(\"sub-agent task should finish\");\n    assert_eq!(\n        calls.load(Ordering::SeqCst),\n        SUBAGENT_API_TIMEOUT_MAX_RETRIES.saturating_add(1) as usize,\n        \"needs-input interruption must not park for continuation; the API call \\\n         is retried up to the timeout-retry budget, then stops\"\n    );\n\n    let interrupted = {\n        let manager = manager.read().await;\n        manager\n            .get_result(&agent_id)\n            .expect(\"agent should stay registered\")\n    };\n    assert!(matches!(interrupted.status, SubAgentStatus::Interrupted(_)));\n    let checkpoint = interrupted\n        .checkpoint\n        .as_ref()\n        .expect(\"timeout should preserve checkpoint\");","sourceCodeStart":8998,"sourceCodeEnd":9034,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/subagent/tests.rs#L8998-L9034","documentation":"Panic from `.expect(\"sub-agent task should finish\")` on the JoinHandle result: the outer timeout passed but the task itself returned Err (joined error / panicked inside the task). The task completed after the timeout interruption but failed instead of finishing cleanly.","triggerScenarios":"task_handle resolves to Err because the spawned sub-agent task panicked or returned an error after exhausting timeout retries, e.g., unwrap on an expected channel/message that was consumed by the mailbox drain.","commonSituations":"Task panic on a closed channel after retries exhausted; assertion inside the task failing on the final attempt; JoinError from an aborted task.","solutions":["Run with RUST_BACKTRACE=1 and read the inner task panic to find the failing point inside the sub-agent task","Check that the retry-exhausted path returns Ok (finished status) rather than erroring","Verify channels the task depends on are not closed/dropped before the final retry completes"],"exampleFix":"// before\n.await.expect(\"sub-agent task must not park waiting for checkpoint input\")\n    .expect(\"sub-agent task should finish\");\n// after\n.await.unwrap_or_else(|_| panic!(\"task did not complete in time\"))\n    .unwrap_or_else(|e| panic!(\"task finished with error: {e}\"));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match tokio::time::timeout(Duration::from_secs(5), task_handle).await {\n    Ok(Ok(())) => {},\n    Ok(Err(e)) => panic!(\"task errored: {e}\"),\n    Err(_) => panic!(\"task timed out\"),\n}","preventionTips":["Return a typed terminal status instead of panicking inside spawned tasks","Include the JoinError in assertion messages","Avoid unwrap on channels near the retry-exhaustion boundary"],"tags":["rust","tokio","subagent","task-join"],"backgroundTag":"internal-invariant-violation","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}