EpicGames/lore · error
Failed to write header
Error message
Failed to write header
What it means
This panic comes from the test helper request(): SendStream::write returned Err and the harness expects it with this message. Writing to a QUIC send stream fails when the connection has been closed, the stream has been stopped/reset by the peer, or the send window cannot be advanced.
Solutions
- Inspect the returned WriteError (ClosedStream vs ConnectionLost vs Stopped) to pick the fix
- Ensure the server handler keeps the recv stream open until it has read the full header
- Check the server didn't close the connection due to a handler panic (check server logs/JoinHandles)
- Reorder the test so request() is called before any step that stops or finishes the stream
Example fix
// before
send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes()).await.expect("Failed to write header");
// after
if let Err(e) = send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes()).await {
panic!("Failed to write header: {:?}, connection reason: {:?}", e, connection.close_reason());
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the stream is still writable
if harness.connection.close_reason().is_some() {
panic!("cannot write header: connection closed");
} Type guard
fn stream_writable(conn: &quinn::Connection) -> bool { conn.close_reason().is_none() } Try / catch
match send.write(&header.to_bytes()).await {
Ok(_) => (),
Err(quinn::WriteError::Stopped(code)) => panic!("peer stopped stream: {code}"),
Err(e) => panic!("Failed to write header: {e:?}"),
} Prevention
- Never stop/reset the server-side stream before the client finishes writing the header
- Check server handler panics first when writes start failing
- Keep header size in sync with the reader's expectations
- Hold server handles for the full test lifetime
When it happens
Trigger: Calling send.write() on a stream whose connection was closed by the peer, after recv-side issued StopSending causing the stream to reset, or after writing to an already-finished stream.
Common situations: Server handler called stop/reset on the stream before reading the header; server process dropped the connection mid-test; test writes a header after the handler already returned and closed the stream.
Related errors
- Failed flush
- Failed to read response
- Failed to open additional stream
- Failed Quinn server start
- Failed to create client endpoint
AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13).
Data as JSON: /api/errors/8cffbd6c0a70a6c8.
Report an issue: GitHub.
Appendix: source
Thrown at lore-server/src/quic/stream_handler.rs:849
_server: QuinnServer,
_endpoint: Endpoint,
}
impl Harness {
/// Open an additional stream on the same connection.
async fn open_stream(&self) -> (SendStream, RecvStream) {
self.connection
.open_bi()
.await
.expect("Failed to open additional stream")
}
}
/// Send a request asking the handler for `behaviour`.
async fn request(send: &mut SendStream, behaviour: MockBehaviour, command_id: u32) {
send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes())
.await
.expect("Failed to write header");
send.flush().await.expect("Failed flush");
}
/// Read one response header.
async fn response(recv: &mut RecvStream) -> CommandHeader {
let mut buffer = [0u8; 8];
recv.read_exact(&mut buffer)
.await
.expect("Failed to read response");
CommandHeader::from_bytes(&buffer)
}
/// Serve `factory` for `protocol` on a loopback endpoint and open a client stream to it.
///
/// The client skips certificate verification and presents none of its own; the mTLS tests
/// build their endpoints by hand because varying exactly that is what they test.
async fn serve_and_connect(
factory: Box<dyn StreamHandlerFactory>,View on GitHub (pinned to 074eb0b0d1)