github/copilot-sdk · error

failed to write a frame to the in-process runtime connection

Error message

failed to write a frame to the in-process runtime connection

What it means

This AsyncWrite impl for the in-process runtime connection returns a BrokenPipe io::Error when shared.write_frame(buf) reports the frame could not be queued — i.e. the runtime side of the in-process channel is gone (receiver dropped or shut down). The error surfaces to Tokio-based writers as a failed write on the connection, which typically tears the connection down with a BrokenPipe classification.

Solutions

  1. Ensure the runtime is alive before writing: keep the runtime handle/join guard alive for the connection's lifetime and shut down clients first, runtime last.
  2. Treat the BrokenPipe error as terminal: recreate the client/connection (restart the runtime) rather than retrying the same connection.
  3. Drain in-flight requests and close connections gracefully before dropping/shutting down the runtime side.
  4. Check application logs for a runtime-side panic or early exit that dropped the receiver unexpectedly.

Example fix

// before
let conn = Connection::in_process(&runtime);
spawn(worker(conn.clone()));
drop(runtime); // receiver gone; next write -> BrokenPipe

// after
let conn = Connection::in_process(&runtime);
let worker = spawn(worker(conn.clone()));
worker.await?; // drain requests first
drop(conn);
drop(runtime);
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: check the runtime is still running before writing
fn connection_usable(runtime: &RuntimeHandle) -> bool { runtime.is_running() }

Type guard

fn is_connected(conn: &Connection) -> bool { !conn.is_closed() }

Try / catch

match conn.send_frame(&buf).await {
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
        // receiver dropped: recreate runtime connection and retry once
        let conn = reconnect()?;
        conn.send_frame(&buf).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Writing to the in-process runtime connection after the runtime task/handle has been dropped or shut down; issuing requests concurrently with runtime shutdown; poll_write invoked on a connection whose shared state already closed its receiver.

Common situations: Shutting down the embedded runtime while requests are still in flight; holding a client/connection across a runtime restart; a panic or early return in the runtime loop dropping the receiver while the caller keeps sending frames.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/7737c07b783adfb2. Report an issue: GitHub.

Appendix: source

Thrown at rust/src/ffi.rs:190

    }
}

/// Write side of the FFI transport. Each frame is forwarded synchronously to
/// the native `connection_write` export (native copies before returning).
pub(crate) struct FfiWriter {
    shared: Arc<FfiShared>,
}

impl AsyncWrite for FfiWriter {
    fn poll_write(
        self: Pin<&mut Self>,
        _cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<std::io::Result<usize>> {
        if self.shared.write_frame(buf) {
            Poll::Ready(Ok(buf.len()))
        } else {
            Poll::Ready(Err(std::io::Error::new(
                std::io::ErrorKind::BrokenPipe,
                "failed to write a frame to the in-process runtime connection",
            )))
        }
    }

    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
        Poll::Ready(Ok(()))
    }

    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
        Poll::Ready(Ok(()))
    }
}

/// Prepared FFI host. The cdylib is loaded process-globally and never unloaded
/// (see [`load_library`]).
pub(crate) struct FfiHost {

View on GitHub (pinned to cd8cf15dc3)