{"record":{"id":"4e385484bdaca9b4","repo":"astrid-runtime/astrid","slug":"event-bus-closed-before-response-on-want-response","errorCode":null,"errorMessage":"event bus closed before response on {want_response}","messagePattern":"event bus closed before response on (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/bus_admin.rs","lineNumber":157,"sourceCode":"            message: msg,\n        });\n\n        let deadline = tokio::time::Instant::now()\n            .checked_add(self.timeout)\n            .unwrap_or_else(tokio::time::Instant::now);\n\n        loop {\n            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());\n            if remaining.is_zero() {\n                return Err(anyhow!(\n                    \"bus admin request timed out after {:?} waiting for {want_response}\",\n                    self.timeout\n                ));\n            }\n            let event = match tokio::time::timeout(remaining, receiver.recv()).await {\n                Ok(Some(ev)) => ev,\n                Ok(None) => {\n                    return Err(anyhow!(\n                        \"event bus closed before response on {want_response}\"\n                    ));\n                },\n                Err(_) => {\n                    return Err(anyhow!(\n                        \"bus admin request timed out after {:?} waiting for {want_response}\",\n                        self.timeout\n                    ));\n                },\n            };\n\n            let AstridEvent::Ipc { message, .. } = &*event else {\n                continue;\n            };\n            if message.source_id != self.expected_source_id {\n                continue;\n            }\n            // The kernel's `publish_response` wraps the","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/bus_admin.rs#L139-L175","documentation":"BusAdminClient::request subscribes to the response topic before publishing the request. If receiver.recv() returns Ok(None), the broadcast channel's sender side has been dropped — the event bus shut down — so no response can ever arrive. This error reports that the bus closed while the request was still in flight for the given response topic.","triggerScenarios":"Calling request() while the event bus / its publisher is being shut down; the bus handle holding the sender is dropped mid-request (e.g. process teardown, a test dropping the bus); recv on a broadcast Receiver whose Sender has gone away.","commonSituations":"Graceful shutdown racing an in-flight admin request; unit/integration tests dropping the bus fixture before awaiting the response; kernel process exit closing the shared bus.","solutions":["Ensure the event bus outlives all in-flight admin requests (hold the sender until requests complete)","Check for premature shutdown/teardown ordering in the gateway startup/shutdown code","Retry the request after the bus is re-established, or surface a clean 'shutting down' state to the caller","In tests, await requests before dropping the bus fixture"],"exampleFix":"// before\ndrop(bus); // sender dropped while request in flight\nlet resp = client.request(kind).await?;\n// after\nlet resp = client.request(kind).await?; // keep bus alive until response\ndrop(bus);","handlingStrategy":"try-catch","validationCode":"// Check the bus is alive before issuing:\nif bus_sender_count() == 0 { return Err(anyhow!(\"event bus unavailable\")); }","typeGuard":null,"tryCatchPattern":"match client.request(kind).await {\n    Ok(resp) => resp,\n    Err(e) if e.to_string().contains(\"event bus closed\") => {\n        // treat as shutdown: stop issuing requests, surface clean shutdown state\n        Err(anyhow!(\"gateway is shutting down\"))\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep the bus sender alive for the lifetime of all in-flight admin requests","Coordinate shutdown: drain admin requests before dropping the bus","In tests, always await client futures before dropping fixtures"],"tags":["event-bus","shutdown","async"],"backgroundTag":"broken-pipe","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"}