Codex turns fail with a misleading capacity error when the workspace path contains CJK characters (header 'x-codex-turn-metadata' has invalid value)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- api
Research direction
Read src/codex/forward-transport-headers.ts around the failing Headers.set call and review draft PR #8040, which the issue says addresses the same root cause. Check whether its regression coverage includes a CJK workspace path; done means turn metadata can be serialised in the header and its values survive parsing, without the misleading capacity failure.
Written by the indexing model from the issue text.
Description
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 showedWe’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
- Put a git repository under a directory whose absolute path contains non-ASCII characters, for example
/Users/<user>/Documents/示例目录/example-project(git statusclean or dirty, either way). - Configure Codex to use the proxy, then open that directory as the workspace and send any message, e.g.
codex exec "只回复两个字:你好". - 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.
- The proxy log (
~/.opencodex/service.log) shows the actual cause, once per attempt, with the source line atsrc/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
- 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 #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:
- This is the same root cause as PR #8040, which is still a draft and whose regression test covers Korean and emoji via
JSON.parse/re-serialise. A CJK workspace path exercises the same branch, so the existing fix likely resolves this report too; I am filing it because the searchable symptom here (the capacity message) is what users actually see, and because #8042 references the PR but no issue documented the CJK case. - Worth keeping in mind for coverage: the client-side error mapping is the reason this is expensive to diagnose. Turning an unrepresentable header into a capacity message is indistinguishable from real throttling.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 1.3k
- Avg merge
- 16h 42m
- Merged PRs (30d)
- 695
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lidge-jun/opencodex
-
bug provider tools
Difficulty 4/5 3-5 days Newbie friendliness 52/100
Maintainers usually reply within 1 day
-
[Bug]: Routed Claude sessions emit call_ tool_use ids, and Remote Control drops their approval cardsOpenbug tools
Difficulty 3/5 1-2 days Newbie friendliness 67/100
lidge-jun/opencodex#8051 · 1 comment ·
Maintainers usually reply within 1 day
-
account-pool bug
Difficulty 4/5 3-5 days Newbie friendliness 45/100
lidge-jun/opencodex#8042 · 4 comments · 1 reaction ·
Maintainers usually reply within 1 day
-
bug gui proxy service
Difficulty 5/5 Over a week Newbie friendliness 28/100
lidge-jun/opencodex#7967 · 4 comments ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 25/100
lidge-jun/opencodex#7965 · 1 comment ·
Maintainers usually reply within 1 day
All issues in lidge-jun/opencodex
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Northeastern-Electric-Racing/FinishLine#4660 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
excaliburjs/Excalibur#3895 ·
Maintainers usually reply within 3 days
-
infer_audio_type only recognises one MPEG Layer III frame header, so most MP3 files are rejectedPossibly taken @Steve-20-ai claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Earn report crashes with "RangeError: Invalid time value" when a report row has an unparsable timestampPossibly taken @Gurkaran18 claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
joinmarket-webui/jam#1588 ·
Maintainers usually reply within 3 days
-
BeOS .json missingOpen
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
mihaip/infinite-mac#475 ·