github/copilot-sdk · error
writer actor dropped ack without responding
Error message
writer actor dropped ack without responding
What it means
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.
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
Example fix
// before
let result = client.write(frame).await; // BrokenPipe: writer dropped ack
// after
match client.write(frame).await {
Err(e) if e.to_string().contains("writer actor") => {
client = reconnect().await?;
client.write(frame).await?;
}
r => r?,
} Defensive patterns
Strategy: retry
Try / catch
// Rust
match client.write(frame).await {
Err(e) if e.to_string().contains("dropped ack") => {
// outcome unknown: reconnect and resend idempotent requests only
client = Client::connect(endpoint).await?;
}
other => other?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to write a frame to the in-process runtime connection
- writer actor has shut down
- runtime.shutdown timed out during Client::stop
- Cannot log after the factory run has settled
- Request cancelled by runtime
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/d9d1726d9a94e056.
Report an issue: GitHub.
Appendix: source
Thrown at rust/src/jsonrpc.rs:672
frame.extend_from_slice(CONTENT_LENGTH_HEADER.as_bytes());
frame.extend_from_slice(body.len().to_string().as_bytes());
frame.extend_from_slice(b"\r\n\r\n");
frame.extend_from_slice(&body);
let (ack_tx, ack_rx) = oneshot::channel();
self.write_tx
.send(WriteCommand { frame, ack: ack_tx })
.map_err(|_| {
Error::from(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"writer actor has shut down",
))
})?;
match ack_rx.await {
Ok(Ok(())) => Ok(()),
Ok(Err(e)) => Err(Error::from(e)),
Err(_) => Err(Error::from(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"writer actor dropped ack without responding",
))),
}
}
}
/// RAII guard that removes a pending-request entry from the map if the
/// owning future is dropped before the response arrives. Disarmed on the
/// happy path so the read loop's response handling owns the cleanup.
struct PendingGuard<'a> {
map: &'a RwLock<HashMap<u64, PendingRequest>>,
id: u64,
armed: bool,
}
impl PendingGuard<'_> {
fn disarm(&mut self) {View on GitHub (pinned to cd8cf15dc3)