{"record":{"id":"d9d1726d9a94e056","repo":"github/copilot-sdk","slug":"writer-actor-dropped-ack-without-responding","errorCode":null,"errorMessage":"writer actor dropped ack without responding","messagePattern":"writer actor dropped ack without responding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/src/jsonrpc.rs","lineNumber":672,"sourceCode":"        frame.extend_from_slice(CONTENT_LENGTH_HEADER.as_bytes());\n        frame.extend_from_slice(body.len().to_string().as_bytes());\n        frame.extend_from_slice(b\"\\r\\n\\r\\n\");\n        frame.extend_from_slice(&body);\n\n        let (ack_tx, ack_rx) = oneshot::channel();\n        self.write_tx\n            .send(WriteCommand { frame, ack: ack_tx })\n            .map_err(|_| {\n                Error::from(std::io::Error::new(\n                    std::io::ErrorKind::BrokenPipe,\n                    \"writer actor has shut down\",\n                ))\n            })?;\n\n        match ack_rx.await {\n            Ok(Ok(())) => Ok(()),\n            Ok(Err(e)) => Err(Error::from(e)),\n            Err(_) => Err(Error::from(std::io::Error::new(\n                std::io::ErrorKind::BrokenPipe,\n                \"writer actor dropped ack without responding\",\n            ))),\n        }\n    }\n}\n\n/// RAII guard that removes a pending-request entry from the map if the\n/// owning future is dropped before the response arrives. Disarmed on the\n/// happy path so the read loop's response handling owns the cleanup.\nstruct PendingGuard<'a> {\n    map: &'a RwLock<HashMap<u64, PendingRequest>>,\n    id: u64,\n    armed: bool,\n}\n\nimpl PendingGuard<'_> {\n    fn disarm(&mut self) {","sourceCodeStart":654,"sourceCodeEnd":690,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/rust/src/jsonrpc.rs#L654-L690","documentation":"After write enqueues a frame, it awaits a oneshot ack from the writer actor confirming the bytes were flushed. If the writer task is dropped before sending on that ack channel (task aborted, panic, or shutdown race), the recv returns RecvError and the library reports it as a BrokenPipe, since the write outcome is unknowable.","triggerScenarios":"The writer actor task is dropped between receiving the WriteCommand and replying on the ack oneshot — typically when the runtime is shutting down or the actor loop exits on a prior flush error while a write is in flight.","commonSituations":"Cancelling/shutting down the tokio runtime while a request is mid-flight; a panic or early return in the writer loop; dropping the Client without draining pending writes.","solutions":["Treat as a connection-loss signal: reconnect and re-send the request (it may or may not have been delivered)","Avoid dropping/shutting down the runtime while writes are pending; use graceful shutdown with ack draining","Keep the Client alive until all awaited writes resolve","Enable debug logging on the writer task to find the earlier error that ended the actor"],"exampleFix":"// before\nlet result = client.write(frame).await; // BrokenPipe: writer dropped ack\n// after\nmatch client.write(frame).await {\n    Err(e) if e.to_string().contains(\"writer actor\") => {\n        client = reconnect().await?;\n        client.write(frame).await?;\n    }\n    r => r?,\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust\nmatch client.write(frame).await {\n    Err(e) if e.to_string().contains(\"dropped ack\") => {\n        // outcome unknown: reconnect and resend idempotent requests only\n        client = Client::connect(endpoint).await?;\n    }\n    other => other?,\n}","preventionTips":["Keep the tokio runtime alive until all writes complete","Use graceful shutdown that drains pending write acks","Only re-send idempotent requests after this error — delivery is ambiguous"],"tags":["rust","async","broken-pipe","shutdown-race"],"backgroundTag":"broken-pipe","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}