openai / openai/codex-plugin-cc
Windows zh-TW: codex app-server JSONL parser crashes on Big5-encoded taskkill stdout leak
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 33.3k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
Bug
On Windows systems with a non-English (specifically zh-TW / CP-950 / Big5) system locale, /codex:review, /codex:task, and the stop-time review gate fail with:
Failed to parse codex app-server JSONL: Unexpected token '�', "���\: PID "... is not valid JSON
The companion process exits with status 1 and the connection is torn down. Subsequent retries are racy: they sometimes succeed (when an existing broker happens to absorb the request), but a fresh broker spawn reliably reproduces the failure.
Reproduction
- Windows + zh-TW (Traditional Chinese / Taiwan) system locale (
chcpreturns950). - Have any project that starts the codex CLI with at least one MCP server that fails to start (e.g. an offline server, a misconfigured one, or one with handshake errors). On my machine
unityMCP,cheatengine, andida-pro-mcpall fail; any one of them is enough. - Tear down any running broker (
%LOCALAPPDATA%\Temp\cxc-*\broker.pid) so the next companion invocation has to spawn a fresh one. - From the project directory:
node "<plugin-cache>/scripts/codex-companion.mjs" task --json "anything"
The companion fails immediately with the parse error above. Re-running the same command after a successful broker is up succeeds — broker mode does not exercise the leaky path.
Root cause
The JSONL parser in plugins/codex/scripts/lib/app-server.mjs handleLine() calls JSON.parse() on every line received from codex.exe's stdout pipe and, on failure, calls handleExit() which rejects every in-flight request and tears down the connection.
Capturing raw bytes from inside app-server-broker.mjs's internal SpawnedCodexAppServerClient (full hex dump below), the offending line is not corrupted JSON — it is a complete localized Windows OS message that has nothing to do with the protocol:
hex = a6 a8 a5 5c 3a 20 50 49 44 20 ac b0 20 31 32 33 34 ...
cp950 = 成功: PID 為 1234 (PID 為 5678 的子處理程序) 的處理程序已終止。
utf-8 = ���\: PID �� 1234 (PID �� 5678 ���l�B�z�{��) ...
That is the success message printed by taskkill /T /F /PID xxxx — Windows' built-in process-tree killer — under the system locale (CP-950 / Big5). The companion process itself does not call taskkill on this code path, so the bytes have to be coming from inside codex.exe itself.
Walking the app-server-broker.mjs log on the failing run confirms it (timestamps abridged):
SPAWN proc.pid=77412 transport=direct
STDOUT len=209 "{\"id\":1,\"result\":{\"userAgent\":\"...\",\"codexHome\":...}}" ← initialize ok
STDOUT len=94 "{\"method\":\"remoteControl/status/changed\",...}" ← ok
STDOUT len=652 "{\"method\":\"mcpServer/startupStatus/updated\",...starting...}" ← ok
STDERR len=53 "���~: ��..." // CP-950 "錯誤: 找不到..." (taskkill ENOENT path)
STDOUT len=246 "{\"method\":\"mcpServer/startupStatus/updated\",...failed...}" ← ok
...
STDOUT len=111 "���\: PID �� 70260 (PID �� 1396 ��...) ..." ← LEAK
BAD LINE transport=direct ← parser tears the connection down
The leak appears immediately after a mcpServer/startupStatus/updated-failed notification. The most plausible explanation is that the upstream codex CLI invokes taskkill to clean up an MCP child whose handshake failed, and incorrectly leaves taskkill's stdout connected to its own stdout (instead of capturing it or redirecting to NUL). On en-US Windows the leaked line happens to be ASCII (SUCCESS: ...) but still not JSON, so issue #23-style failures (different first byte) are the same bug class.
This is therefore primarily an upstream codex-CLI bug. I'll file it at openai/codex separately, link from here.
Why this is distinct from #23 / #24 / #97 / #171
#23 reports the same outer symptom (Failed to parse codex app-server JSONL) but the leak source there is shell-init noise (\x1b[?2004h, bracketed paste mode). The proposed fixes (#24, #97, #171) all add stripAnsi() and re-parse. That regex would not match the bytes above — they are not ANSI escapes, they are localized OS text — so JSONL parsing still fails after stripping.
Both bug classes share the same correct shape of fix at the plugin layer, though: lines that cannot possibly be JSONL records should be dropped, not treated as protocol violations that tear the connection down. A JSONL record always begins with { or [. Any line whose first non-whitespace, post-stripAnsi character is something else is definitively garbage from outside the protocol.
A PR that implements that one-line guard (and subsumes #23) is incoming.
Why it's racy
Once a broker is alive, all subsequent connections from the companion go over the named pipe and never spawn a direct codex.exe in the parent process — so the leaky stdout never flows through the JSONL parser of the parent. The leak is still happening inside the broker process, but is parsed there and fails the same way; the broker just dies more quietly.
This also explains why running the same command twice in a row often shows different outcomes (first call: broker bootstrap fails on the leak; second call: existing broker handles it).
Environment
| OS | Windows 11 Pro 26200, zh-TW system locale (CP-950) |
| Shell | PowerShell 7 + Git Bash (both reproduce) |
| Node | v22.16.0 |
| codex CLI | 0.130.0 |
| codex-plugin-cc | 1.0.4 |
| Reproducer | runs taskkill ⇒ Big5 success line bytes a6 a8 a5 5c 3a 20 50 49 44 ... |
Workaround
Disable the stop-time review gate (/codex:setup --disable-review-gate) until a fix lands, OR keep a long-lived broker alive (do not let %LOCALAPPDATA%\Temp\cxc-* go stale).
Contributor guide
No contributing guide indexed for this repository
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.
Research direction
Start in plugins/codex/scripts/lib/app-server.mjs at handleLine(), then reproduce with the provided codex-companion.mjs task command on the stated Windows locale. Confirm that non-JSONL taskkill output no longer tears down the connection while valid JSONL notifications continue to parse.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100