{"record":{"id":"3802c4137da82de5","repo":"astrid-runtime/astrid","slug":"stand-in-capsule-joins","errorCode":null,"errorMessage":"stand-in capsule joins","messagePattern":"stand-in capsule joins","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/routes/sessions_tests.rs","lineNumber":782,"sourceCode":"    });\n\n    let body = serde_json::json!({ \"title\": \"renamed\" });\n    let payload = build_update_payload(correlation_id, \"sess-1\", &body).unwrap();\n    let value = request_capsule(\n        &bus,\n        TOPIC_UPDATE_REQUEST,\n        &response_topic,\n        payload,\n        correlation_id,\n        &principal,\n        None,\n        &[test_provider_source_id()],\n        CAPSULE_TIMEOUT,\n    )\n    .await\n    .expect(\"helper returns the scoped reply\");\n\n    capsule.await.expect(\"stand-in capsule joins\");\n    let summary = parse_session_field(&value)\n        .unwrap()\n        .expect(\"present session\");\n    assert_eq!(summary.session_id, \"sess-1\");\n    assert_eq!(summary.title.as_deref(), Some(\"renamed\"));\n}\n\n/// A reply stamped for a different principal is ignored. This protects a\n/// same-topic reply from satisfying another caller's request.\n#[tokio::test]\nasync fn request_capsule_ignores_wrong_principal_reply() {\n    let bus = Arc::new(EventBus::new());\n    let principal = PrincipalId::new(\"alice\").expect(\"valid principal\");\n    let correlation_id = \"corr-want-principal\";\n    let response_topic = format!(\"{TOPIC_LIST_RESPONSE_PREFIX}.{correlation_id}\");\n\n    let bus_bg = Arc::clone(&bus);\n    let resp_topic = response_topic.clone();","sourceCodeStart":764,"sourceCodeEnd":800,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/sessions_tests.rs#L764-L800","documentation":"This panic comes from `capsule.await.expect(\"stand-in capsule joins\")` in `request_capsule_round_trips_update_reply`. Joining the spawned stand-in capsule task yielded Err (or the JoinHandle was aborted), which in tokio means the task itself panicked or was cancelled. It usually indicates the assertions inside the capsule (request shape, principal stamp) failed first.","triggerScenarios":"The inner capsule task panicked — e.g. its `expect(\"request arrives\")`, the `AstridEvent::Ipc` pattern-match `panic!(\"expected IPC request\")`, or `assert_eq!(message.principal.as_deref(), Some(\"alice\"))` failed — so JoinHandle::await returns JoinError.","commonSituations":"The helper sends a differently shaped event (not AstridEvent::Ipc); patch contains extra keys so the present-keys-only assertion fails; the event's principal stamp is missing or wrong; task aborted because the test future was dropped early.","solutions":["Run with `cargo test -- --nocapture` to see the inner capsule panic message, which names the real failing assertion.","Fix the helper/event so the delivered AstridEvent::Ipc carries principal Some(\"alice\"), non-nil source_id, and only the patched key.","Replace the expect with match on the JoinError to report the inner panic explicitly.","Ensure the test does not return/abort before joining the capsule task."],"exampleFix":"// before\ncapsule.await.expect(\"stand-in capsule joins\");\n// after\nif let Err(e) = capsule.await {\n    panic!(\"stand-in capsule joins: {e}\");\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(e) = capsule.await {\n    panic!(\"stand-in capsule joins: {e}\");\n}","preventionTips":["Run failing tests with --nocapture to expose the inner panic","Keep capsule assertions narrow and message-rich","Join spawned tasks before the test future ends to avoid aborts"],"tags":["rust","tokio","test","panic","join-error"],"backgroundTag":"async-task-panicked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}