openai/codex · error · StreamableHttpClientAdapterError

invalid HTTP header: {0}

Error message

invalid HTTP header: {0}

What it means

Header(String) wraps a failure to build an http::HeaderValue for headers the adapter must construct itself: Accept, Content-Type, Authorization: Bearer <token>, Mcp-Session-Id, and Last-Event-Id (all set through insert_header). HeaderValue::from_str rejects anything outside visible ASCII plus space/tab — control bytes, CR/LF, DEL, non-ASCII — so the culprit is almost always a token or session id carrying a stray newline or invisible character.

Source

Thrown at codex-rs/rmcp-client/src/http_client_adapter.rs:110

    cancellations: Arc<Mutex<HashMap<RequestId, oneshot::Sender<()>>>>,
}

impl Drop for EventStreamCancellation {
    fn drop(&mut self) {
        self.cancellations
            .lock()
            .unwrap_or_else(PoisonError::into_inner)
            .remove(&self.request_id);
    }
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum StreamableHttpClientAdapterError {
    #[error("streamable HTTP session expired with 404 Not Found")]
    SessionExpired404,
    #[error(transparent)]
    HttpRequest(#[from] ExecServerError),
    #[error("invalid HTTP header: {0}")]
    Header(String),
    #[error("MCP response body exceeds {maximum_bytes} bytes")]
    ResponseTooLarge { maximum_bytes: usize },
}

impl StreamableHttpClientAdapter {
    pub(crate) fn new(
        http_client: Arc<dyn HttpClient>,
        default_headers: HeaderMap,
        auth_provider: Option<SharedAuthProvider>,
        has_configured_headers: bool,
        redirect_mode: StreamableHttpRedirectMode,
        initialize_deadline: Arc<Mutex<Option<Instant>>>,
    ) -> Self {
        Self {
            http_client: Arc::new(SameOriginRedirectHttpClient::new(http_client)),
            default_headers,
            auth_provider,

View on GitHub (pinned to 339751715c)

Solutions

  1. trim() tokens/session ids before passing them to the client
  2. Validate the value is visible-ASCII (bytes 0x20–0x7E plus tab) before the call
  3. If a server-issued id is the culprit, capture it exactly as the server sent it and report the server bug
  4. Add a unit test asserting your token source never contains \r or \n

Example fix

// before
let token = std::fs::read_to_string("token.txt")?; // trailing '\n' -> invalid HTTP header

// after
let token = std::fs::read_to_string("token.txt")?.trim().to_string();
assert!(token.bytes().all(|b| b == b'\t' || (0x20..0x7f).contains(&b)));
Defensive patterns

Strategy: validation

Validate before calling

fn assert_header_value_safe(value: &str) {
    assert!(
        value.bytes().all(|b| b == b'\t' || (0x20..0x7f).contains(&b)),
        "value contains bytes invalid in an HTTP header"
    );
}
// apply to every token/session id before handing it to the client
let token = token.trim();
assert_header_value_safe(&token);

Type guard

fn is_valid_header_value(value: &str) -> bool {
    value.bytes().all(|b| b == b'\t' || (0x20..0x7f).contains(&b))
}

Try / catch

// When the client error surfaces:
if let Some(source) = find_header_error(&error) {
    // strip and re-validate the offending token/session id, then retry once
}

Prevention

When it happens

Trigger: An auth token containing \n or other control bytes when rmcp passes it to post_message/get_stream/delete_session; an Mcp-Session-Id or Last-Event-Id returned by a server containing spaces, non-ASCII, or control characters.

Common situations: Secrets read from files or env vars with trailing newlines (the classic $(cat token.txt) mistake); tokens copy-pasted with invisible Unicode; a misbehaving server issuing session ids with unusual bytes; values interpolated from unvalidated config.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/8d9407167eadb3fd. Report an issue: GitHub.