{"record":{"id":"6f857e6369375899","repo":"openai/codex","slug":"network-proxy-attribution-frame-timed-out","errorCode":null,"errorMessage":"network proxy attribution frame timed out","messagePattern":"network proxy attribution frame timed out","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/network-proxy/src/attribution.rs","lineNumber":112,"sourceCode":"        let token_len = stream.read_u16().await? as usize;\n        if token_len == 0 || token_len > MAX_ATTRIBUTION_TOKEN_LEN {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"invalid network proxy attribution token length\",\n            ));\n        }\n        let mut token = vec![0_u8; token_len];\n        stream.read_exact(&mut token).await?;\n        String::from_utf8(token).map_err(|_| {\n            io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"network proxy attribution token is not UTF-8\",\n            )\n        })\n    })\n    .await\n    .map_err(|_| {\n        io::Error::new(\n            io::ErrorKind::TimedOut,\n            \"network proxy attribution frame timed out\",\n        )\n    })??;\n\n    Ok(Some(token))\n}\n\n/// Writes the trusted bridge preface consumed by the shared proxy ingress.\n#[doc(hidden)]\npub fn write_attribution_frame(writer: &mut impl Write, token: &str) -> io::Result<()> {\n    if token.is_empty() || token.len() > MAX_ATTRIBUTION_TOKEN_LEN {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"invalid network proxy attribution token length\",\n        ));\n    }\n    let token_len = u16::try_from(token.len()).map_err(|_| {","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/network-proxy/src/attribution.rs#L94-L130","documentation":"The connection's first byte was the NUL magic, so the ingress started reading a full attribution frame under tokio::time::timeout with ATTRIBUTION_FRAME_TIMEOUT = 3 seconds; the complete frame (8-byte magic + u16 length + token bytes) did not arrive in time and the error is io::ErrorKind::TimedOut. The client connected, sent the first byte(s), then stalled mid-preface.","triggerScenarios":"Writing the magic and then blocking before the rest (computing the token, awaiting something else, half-open TCP); splitting the preface across delayed flushes on a buffered writer; a genuine network stall or middlebox dropping the half-written connection.","commonSituations":"A client that connects early 'to save time' and writes the preface later; a BufWriter that is never flushed; NAT/idle-timeout killing the connection between the first byte and the remainder.","solutions":["Assemble the entire frame first, then send it immediately after connect via write_attribution_frame -- never split the preface across awaits.","Flush the writer if you must buffer manually.","Treat the 3s window as fixed (a compile-time constant): fix the client's write pattern, do not try to tune the timeout."],"exampleFix":"// before: preface split across awaits, magic sent first\nstream.write_all(MAGIC).await?;\nlet token = fetch_token().await; // may stall past the 3s window\nstream.write_all(&frame_body).await?;\n// after: prepare first, then write the whole preface right after connect\nlet token = fetch_token().await;\nwrite_attribution_frame(&mut stream, &token)?;","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// TimedOut on the preface is transient: reconnect and resend promptly\nif let Some(e) = err.downcast_ref::<io::Error>() {\n    if e.kind() == io::ErrorKind::TimedOut\n        && e.to_string().contains(\"attribution frame timed out\")\n    {\n        stream.shutdown().await.ok();\n        // bounded retry: dial again and write the FULL frame immediately\n    }\n}","preventionTips":["Connect only when the token is already available; then write the whole preface at once.","Never split the preface across awaits or leave it in an unflushed BufWriter.","Bound retries (1-2 attempts); a client that stalls once will usually stall again."],"tags":["rust","codex","network-proxy","timeout","handshake","frame"],"backgroundTag":"handshake-timeout","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}