Skip to content

Codex turns fail with a misleading capacity error when the workspace path contains CJK characters (header 'x-codex-turn-metadata' has invalid value) #8050

Closed

Description

@504394123

Client or integration

Codex App (desktop, macOS), Codex CLI 0.166.0-alpha.5.1, installed @bitkyc08/opencodex 2.95.0 with the built-in OpenAI provider pointed at the local proxy (127.0.0.1:10100).

Area

Proxy and routing, specifically the account-pool path that forwards Codex turn metadata.

Summary

Every turn in one project failed after the proxy rejected the request with a vague capacity message, so the cause was invisible from the client: the workspace path contained CJK characters, the path was embedded verbatim in x-codex-turn-metadata, and Headers.set refused it.

  • In /Users/<user>/Documents/示例目录/example-project (a git working tree whose absolute path contains 示例目录), every single turn failed. Codex retried 5/5, then showed We’re currently experiencing high demand, which may cause temporary errors. Four separate threads were opened over 25 minutes and all four failed the same way.
  • In a projectless Codex chat whose directory is pure ASCII (/Users/<user>/Documents/ascii-chat), the same model answered normally.

The workspace path is only attached to request_kind: "turn" requests, so prewarm succeeded and the client looked healthy right up to the first real question. The failure is therefore not upstream capacity, and the retry loop sends the identical invalid header five times before giving up.

Grepping ~/.codex/logs_2.sqlite for the four failed threads shows the cwd is the CJK directory in all four, and the rendered client error is exactly the capacity string above, which is why this took a long time to attribute.

Reproduction

  1. Put a git repository under a directory whose absolute path contains non-ASCII characters, for example /Users/<user>/Documents/示例目录/example-project (git status clean or dirty, either way).
  2. Configure Codex to use the proxy, then open that directory as the workspace and send any message, e.g. codex exec "只回复两个字:你好".
  3. Observe that the turn never produces output. The client retries and ends with:
stream disconnected - retrying sampling request (5/5 in 3.34s)... sampling_error=We’re currently experiencing high demand, which may cause temporary errors.
ERROR: We’re currently experiencing high demand, which may cause temporary errors.
  1. The proxy log (~/.opencodex/service.log) shows the actual cause, once per attempt, with the source line at src/codex/forward-transport-headers.ts:65:
60 |   if (installId && installId.trim()) {
61 |     headers.set(CODEX_INSTALLATION_ID_HEADER, deriveCodexAccountInstallationId(chatgptAccountId, installId));
62 |   }
63 |   const turnMeta = headers.get(CODEX_TURN_METADATA_HEADER);
64 |   if (turnMeta) {
65 |     headers.set(CODEX_TURN_METADATA_HEADER, rewriteTurnMetadataInstallationId(turnMeta, chatgptAccountId));
                 ^
TypeError: Header 'x-codex-turn-metadata' has invalid value
POST - http://127.0.0.1:10100/v1/responses failed
  1. Repeat the same prompt from a directory with an ASCII-only path; it succeeds on the first attempt with no retries.

Capturing the real outbound bytes on a local listener shows the header the client sends for a CJK workspace (path abbreviated):

{"installation_id":"…","request_kind":"turn","workspaces":{"/Users/<user>/Documents/示例目录/example-project":{"latest_git_commit_hash":"…","has_changes":true}},"model":"a6-ds/deepseek-v4.1-flash-1017"}

A minimal, dependency-free reproducer of the failing line — rewriteTurnMetadataInstallationId re-serialises the parsed record, so the CJK path is decoded back to literal characters, and the subsequent Headers.set rejects any code unit above U+00FF:

const meta = JSON.stringify({ installation_id: "abc", workspaces: { "/tmp/中文/项目": {} } });
const rewritten = JSON.stringify({ ...JSON.parse(meta), installation_id: "xyz" });
new Headers({ "x-codex-turn-metadata": rewritten }); // TypeError: Header 'x-codex-turn-metadata' has invalid value

Expected behavior

A workspace path containing CJK characters must work exactly like an ASCII path: the turn reaches the provider, and the metadata round-trips with its values intact. Two supporting expectations:

  • Header serialisation should be ASCII-safe, which is what PR Fix non-ASCII Codex turn metadata headers #8040 already does for the parsed-record path.
  • If a header still cannot be serialised, the failure should surface as an actionable error naming the header and the offending value rather than being reported to the client as upstream capacity, since that message invites five pointless retries and sends users looking at their provider quota.

Version

@bitkyc08/opencodex 2.95.0. Client: Codex App / Codex CLI 0.166.0-alpha.5.1.

Operating system

macOS 26.7.1 (build 25G241), Apple silicon, bundled Bun 1.4.4.

Logs or error output

Proxy log, repeating for every attempt (~/.opencodex/service.log):

TypeError: Header 'x-codex-turn-metadata' has invalid value
      at isolateCodexPoolAccountHeaders (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/codex/forward-transport-headers.ts:65:13)
      at materializeCodexUpstreamAuth (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/codex/auth-context.ts:1850:5)
      at resolveFirstUsableOpenAiSidecar (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/providers/openai-sidecar.ts:253:25)
      at async prepareResponsesSidecarAuth (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/server/responses/request-sidecar-auth.ts:94:29)
      at async <anonymous> (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/server/responses/core-pipeline.ts:56:34)
      at async handleResponsesInner (<npm-prefix>/lib/node_modules/@bitkyc08/opencodex/src/server/responses/core-pipeline.ts:126:7)
POST - http://127.0.0.1:10100/v1/responses failed

Count of that header error in my log after one afternoon of use: 483, all from the one CJK project.

Client side, the turn status recorded for the four failed threads is failed with We’re currently experiencing high demand, which may cause temporary errors. and 27s, 9s, 23s, 27s durations respectively — i.e. the full retry budget each time.

Screenshots and supporting files

No screenshots; the defect is fully described by the request path, the proxy stack trace, and the captured header above. I can attach a redacted proxy log excerpt if that helps.

Redacted configuration

Default single-account pool on the local machine, no custom routing:

{
  "model": "a6-ds/deepseek-v4.1-flash-1017",
  "openai_base_url": "http://127.0.0.1:10100/v1",
  "codexAccounts": [{ "id": "chatgpt-…", "plan": "plus", "isMain": false }],
  "activeCodexAccountId": "chatgpt-…",
  "pausedCodexAccountIds": ["__main__"]
}

Additional context

A local workaround on 2.95.0 that has held up over repeated real turns: escape every code unit above U+00FF to \uXXXX immediately before writing the header. For a CJK path this reproduces the reported symptom before the change (TypeError: Header 'x-codex-turn-metadata' has invalid value) and passes after it, with the path string comparing equal after JSON.parse:

function toHeaderSafeJson(value: string): string {
  let out = "";
  for (const ch of value) {
    const code = ch.codePointAt(0)!;
    if (code <= 0xff) { out += ch; continue; }
    const units = code <= 0xffff ? [code] : [0xd800 + ((code - 0x10000) >> 10), 0xdc00 + ((code - 0x10000) & 0x3ff)];
    for (const unit of units) out += String.fromCharCode(92) + "u" + unit.toString(16).padStart(4, "0");
  }
  return out;
}

Two notes for whoever picks this up:

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. coderabbitai commented on Oct 31, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #8040 - Fix non-ASCII Codex turn metadata headers [open]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. zhlf2008 commented on Nov 1, 2026

    @zhlf2008

    Confirming on 2.96.0 (latest, Windows) — still reproducible, and since 2.96 the same crash also takes down non-OpenAI providers.

    Environment: Windows 11 · @bitkyc08/[email protected] (bundled Bun 1.4.4) · Codex app/CLI 0.166.0-alpha.5.1 · workspace E:\工作文件\编程\督察管理系统\yangming-supervision (CJK path).

    Client symptom: identical — Codex retries 5/5, then We're currently experiencing high demand, which may cause temporary errors.

    Server side (~/.opencodex/service.log):

    TypeError: Header 'x-codex-turn-metadata' has invalid value
      at isolateCodexPoolAccountHeaders (.../src/codex/forward-transport-headers.ts:52:13)
      at materializeCodexUpstreamAuth (.../src/codex/auth-context.ts:1849:30)
      at resolveFirstUsableOpenAiSidecar (.../src/providers/openai-sidecar.ts:253:25)
      at async prepareResponsesSidecarAuth (.../src/server/responses/request-sidecar-auth.ts:95:29)
    

    New since 2.96: the call now happens under resolveFirstUsableOpenAiSidecar, which is prepared for every Responses request, so the failure is no longer limited to pooled OpenAI turns. In the same window gemini-3.8-flash (provider google-antigravity) failed every turn with 502 upstream_server_error / headers-only in 128–176 ms — no request ever reached Google. With the header value made ASCII-safe, the identical requests return 200.

    Minimal repro (no Codex needed; the header value itself is pure ASCII — the non-ASCII arrives through \uXXXX escapes, which is what a Rust client emits):

    POST /v1/responses
    content-type: application/json
    x-codex-turn-metadata: {"installation_id":"<uuid>","thread_id":"t","thread_title":"\u9633\u660e"}
    
    {"model":"gpt-6-luna","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"ok"}]}]}
    • metadata without the \uXXXX field → 200
    • with it → 500 in ~20 ms, forward-transport-headers.ts:52 throws

    Cause: rewriteTurnMetadataInstallationId() re-serializes the parsed object with JSON.stringify(), which turns the \uXXXX escapes into literal UTF-8; Headers.set() rejects anything above 0x7F.

    Fix direction (matches #8040 / #8060): escape non-ASCII on the way out, plus keep the defensive drop:

    const NON_ASCII_TEST_RE = /[\u007f-\uffff]/;
    const NON_ASCII_RE = /[\u007f-\uffff]/g;
    
    function toHeaderSafeJson(json: string): string {
      if (!NON_ASCII_TEST_RE.test(json)) return json;
      return json.replace(NON_ASCII_RE, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`);
    }
    
    // forward-transport-headers.ts
    headers.set(CODEX_TURN_METADATA_HEADER,
      toHeaderSafeJson(rewriteTurnMetadataInstallationId(turnMeta, chatgptAccountId)));

    With that applied locally, gpt-6-luna, gpt-6.1-sol and gemini-3.8-flash all return 200 again — including requests whose metadata contains CJK — so the escaping path is sufficient in practice; the try/catch from #8060 covers anything still unparseable.

  3. github-actions commented on Nov 1, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  4. Vladimir321123 commented on Nov 2, 2026

    @Vladimir321123

    Same bug with Cyrillic paths, so it affects any non-ASCII workspace path, not only CJK. Maybe worth widening the title to "non-ASCII".

    Environment: Windows 11, @bitkyc08/opencodex 2.94.0, now 2.96.0 (bundled Bun), Codex CLI 0.164.0, workspace under C:\Users\<user>\Мой диск\....

    Symptom: identical. Codex retries 5/5 and ends with the "high demand" message, while service.log shows TypeError: Header 'x-codex-turn-metadata' has invalid value thrown from isolateCodexPoolAccountHeaders (forward-transport-headers.ts), called via resolveFirstUsableOpenAiSidecar and prepareResponsesSidecarAuth. My log has 240 of these.

    Still present in 2.96.0: rewriteTurnMetadataInstallationId() still ends with return JSON.stringify({ ...record, installation_id: nextId });. Checked against the pristine 2.96.0 tarball: a turn-metadata header whose cwd contains escaped Cyrillic makes isolateCodexPoolAccountHeaders(headers, "acct") throw.

    Local workaround: escaping the re-serialized JSON back to ASCII in that one return statement fixes it. I have been running this since 2.94.0 without side effects:

    return asciiHeaderJson({ ...record, installation_id: nextId });
    
    function asciiHeaderJson(value: unknown): string {
      return JSON.stringify(value).replace(/[\u007f-\uffff]/g, ch => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`);
    }

    The catch is that the edit lives inside the global npm package, so every ocx system update run silently removes it and the failures come back until it is re-applied. It would be great to get #8040 or #8060 merged.

  5. lidge-jun commented on Nov 3, 2026

    @lidge-jun
    Owner

    Fixed on dev by #8040 (7b3bb7a): after the installation_id rewrite, every code unit outside printable ASCII in x-codex-turn-metadata is escaped, so a workspace path with CJK characters no longer makes the header invalid, and the metadata still decodes to the same values. It ships in the next release; please reopen if it still reproduces after updating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions