{"record":{"id":"82f3a366365b0315","repo":"Hmbown/CodeWhale","slug":"sub-agent-join-should-succeed","errorCode":null,"errorMessage":"sub-agent join should succeed","messagePattern":"sub-agent join should succeed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/tests.rs","lineNumber":9155,"sourceCode":"        assignment: make_assignment(),\n        allowed_tools: Some(vec![]),\n        fork_context: false,\n        started_at: Instant::now(),\n        max_steps: 3,\n        token_budget: None,\n        wall_time: DEFAULT_CHILD_WALL_TIME,\n        input_rx: task_input_rx,\n        launch_gate: None,\n        _foreground_child_registration: None,\n    };\n\n    tokio::time::timeout(\n        Duration::from_secs(10),\n        tokio::spawn(run_subagent_task(task)),\n    )\n    .await\n    .expect(\"sub-agent task should finish\")\n    .expect(\"sub-agent join should succeed\");\n\n    assert_eq!(\n        calls.load(Ordering::SeqCst),\n        2,\n        \"one timed-out API attempt should be retried exactly once\"\n    );\n    let snapshot = {\n        let manager = manager.read().await;\n        manager\n            .get_result(&agent_id)\n            .expect(\"agent should stay registered\")\n    };\n    assert_eq!(snapshot.status, SubAgentStatus::Completed);\n    assert_eq!(snapshot.result.as_deref(), Some(\"recovered answer\"));\n}\n\n#[test]\nfn api_timeout_retry_backoff_doubles_and_caps() {","sourceCodeStart":9137,"sourceCodeEnd":9173,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/subagent/tests.rs#L9137-L9173","documentation":"Panic from the second `.expect(\"sub-agent join should succeed\")` at crates/tui/src/tools/subagent/tests.rs:9155: the outer timeout completed (the JoinHandle resolved), but joining the spawned `run_subagent_task` task returned a `JoinError`, meaning the spawned task itself panicked. The inner task's original panic message is the real diagnostic; this expect only reports the join failure.","triggerScenarios":"Inside `run_subagent_task` for `subagent_retries_api_timeout_before_succeeding`: any panic during the 150ms-timeout → retry → complete flow, e.g. an unwrap on a channel send, a snapshot assertion, or an unwrap on manager state in the production task code.","commonSituations":"A regression in the sub-agent retry loop (crates/tui/src/tools/subagent) that unwraps a None/Err on the recovered attempt; channel closed because task_input_tx was dropped while the task expects input; an assertion or expect inside the spawned task firing under the 50ms step timeout.","solutions":["Read the panic printed above this message in the test output — JoinError's cause carries the inner task's panic message and backtrace (`RUST_BACKTRACE=1`).","Inspect `run_subagent_task` for unwrap/expect on the retry path; run it under the delayed_chat_client stub in isolation.","Check that `task_input_tx` is kept alive for the task's lifetime (it is dropped only after the test completes here).","If the inner panic is a timeout-related unwrap, verify the step API timeout (50ms) vs. the mock's 150ms first response still matches the test's intent."],"exampleFix":"// before\n.await\n.expect(\"sub-agent task should finish\")\n.expect(\"sub-agent join should succeed\");\n// after\n.await\n.expect(\"sub-agent task should finish\")\n.unwrap_or_else(|e| panic!(\"sub-agent task panicked: {e}\"));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let handle = tokio::spawn(run_subagent_task(task));\nmatch tokio::time::timeout(Duration::from_secs(10), handle).await {\n    Ok(Ok(())) => {}\n    Ok(Err(join)) => panic!(\"sub-agent task panicked: {join}\"),\n    Err(_) => panic!(\"sub-agent task timed out\"),\n}","preventionTips":["Replace bare .expect on JoinHandle results with a message that includes the JoinError so the inner panic is surfaced.","Run flaky task tests with RUST_BACKTRACE=1 and --nocapture in CI logs.","Keep channel senders (task_input_tx) alive for the spawned task's lifetime.","Avoid unwrap/expect inside production task code paths exercised by tests."],"tags":["rust","tokio","panic","join-error","test-panic"],"backgroundTag":"thread-interrupted","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}