openai/codex · error · io::Error
invalid network proxy attribution token length
Error message
invalid network proxy attribution token length
What it means
The 8-byte magic matched, but the u16 length prefix that follows was 0 or exceeded MAX_ATTRIBUTION_TOKEN_LEN (128 bytes). Rejected with io::ErrorKind::InvalidData. Almost always a writer-side framing bug: wrong endianness (the reader uses tokio's read_u16, i.e. big-endian/network order; a little-endian writer sending a token of length 3 emits 0x0300 = 768), a shifted stream, or garbage after the magic.
Source
Thrown at codex-rs/network-proxy/src/attribution.rs:96
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "empty proxy connection").into());
}
if marker[0] != ATTRIBUTION_FRAME_MAGIC[0] {
return Ok(None);
}
let token = tokio::time::timeout(ATTRIBUTION_FRAME_TIMEOUT, async {
let mut magic = [0_u8; ATTRIBUTION_FRAME_MAGIC.len()];
stream.read_exact(&mut magic).await?;
if &magic != ATTRIBUTION_FRAME_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid network proxy attribution frame",
));
}
let token_len = stream.read_u16().await? as usize;
if token_len == 0 || token_len > MAX_ATTRIBUTION_TOKEN_LEN {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid network proxy attribution token length",
));
}
let mut token = vec![0_u8; token_len];
stream.read_exact(&mut token).await?;
String::from_utf8(token).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"network proxy attribution token is not UTF-8",
)
})
})
.await
.map_err(|_| {
io::Error::new(
io::ErrorKind::TimedOut,
"network proxy attribution frame timed out",View on GitHub (pinned to 339751715c)
Solutions
- Use write_attribution_frame, which writes (token.len() as u16).to_be_bytes(), or replicate big-endian u16 exactly.
- Write the whole frame with a single write_all so fields cannot interleave or shift.
- Keep the token within 1..=128 bytes so the length prefix is valid by construction.
Example fix
// before: little-endian length prefix writer.write_all(&(token.len() as u16).to_le_bytes())?; // reader sees 0x0300 for len 3 // after: big-endian, matching read_u16 network order writer.write_all(&(token.len() as u16).to_be_bytes())?;
Defensive patterns
Strategy: validation
Validate before calling
fn valid_attribution_frame_len(token: &str) -> bool {
let n = token.len();
n >= 1 && n <= 128 && u16::try_from(n).is_ok()
} Type guard
fn valid_attribution_frame_len(token: &str) -> bool {
(1..=128).contains(&token.len())
} Try / catch
Match io::ErrorKind::InvalidData whose message contains 'token length': dump the raw prefix bytes and verify the u16 is big-endian and equals the token length; a shifted stream means the earlier write was partial.
Prevention
- Write the length as big-endian u16 (to_be_bytes), matching read_u16.
- Emit magic + length + token in a single write_all.
- In non-Rust ports, double-check struct.pack endianness ('>H', not '<H').
When it happens
Trigger: A hand-rolled frame writes the length little-endian or as u32 instead of big-endian u16; a truncated or interleaved first write shifts subsequent reads; the client sends the magic followed by unrelated bytes.
Common situations: Porting the preface to Python/Go where struct.pack/native little-endian is the default; test fixtures with hardcoded byte arrays drifting from the wire format; fuzzed input hitting the ingress.
Related errors
- invalid network proxy attribution frame
- network proxy attribution token is not UTF-8
- unknown network proxy attribution token
- network proxy attribution environment mismatch
- empty proxy connection
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/180458353fbdf806.
Report an issue: GitHub.