Resume fails with Responses API 400 when persisted function_call.name contains NUL bytes
Summary
A Codex CLI session became impossible to resume because the persisted transcript contained a function_call.name with embedded NUL bytes. When Codex replayed that history to the Responses API, the API rejected the request with a 400 validation error.
Version
codex-cli 0.133.0
Error
{
"error": {
"message": "Invalid 'input[646].name': string does not match pattern. Expected a string that matches the pattern '^[a-zA-Z0-9_-]+$'.",
"type": "invalid_request_error",
"param": "input[646].name",
"code": "invalid_value"
}
}
Feedback / thread id
019e55fb-87ff-7a30-9d52-010b48f14964
Redacted session evidence
The persisted JSONL session contained a response item like this:
{
"type": "response_item",
"payload": {
"type": "function_call",
"name": "apply\\^@\\^@\\^@\\^@\\^@\\^@",
"call_id": "call_EizYvzgC5R9tmVUJj17m5aDA"
}
}
The same item's arguments also contained many \\^@ sequences, so this appears to be a corrupted persisted tool call, not only a legacy/namespaced tool name.
The following output item was also persisted:
unsupported call: apply\^@\^@\^@\^@\^@\^@
Manual recovery
I backed up the session JSONL and changed only that persisted function_call.name from:
apply\^@\^@\^@\^@\^@\^@
to:
apply_patch
After that, the session JSONL parsed successfully and no function_call.name values violated ^[a-zA-Z0-9_-]+$.
Related issues / prior work
This appears related to:
- #6540
- #10563
- #9447
However, this case is slightly different from namespace normalization such as functions.exec_command: the stored transcript contains embedded NUL bytes and should be treated as corrupted history.
Expected behavior
Codex should not replay invalid historical function_call.name values to the Responses API.
Before sending replayed history or compaction payloads, Codex should validate historical function_call.name values and either:
- sanitize control characters / invalid characters,
- map known corrupted names to valid tool names when safe,
- or drop/convert corrupted historical tool call items so one bad persisted item does not make the whole session impossible to resume.
Suggested regression test
A persisted history item with:
{
"type": "function_call",
"name": "apply\\^@\\^@",
"call_id": "call_test",
"arguments": "{}"
}
should not cause a Responses API request containing an invalid input[*].name.
5 Comments
I pushed a candidate fix branch here:
What it changes:
ResponseItem::FunctionCall.namevalues during context normalization before prompt construction.[a-zA-Z0-9_-]and falls back tounknown_toolif the sanitized name is empty.Validation:
just test -p codex-core normalize_sanitizes_function_call_names_for_prompt"apply\0\0"and"\0\0".1 passed, 2549 skipped; the recipe'sbench-smokestep also exited 0.cargo fmt -- --config imports_granularity=Item --checkexited 0.I attempted to open a draft PR from
cratexnet:codex/sanitize-replayed-function-call-namestoopenai/codex:main, but GitHub rejected PR creation for this account:gh pr create:GraphQL: cratexnet does not have the correct permissions to execute CreatePullRequestPOST /repos/openai/codex/pulls:404 Not Found403 Resource not accessible by integrationThe branch and compare link above should still make the proposed patch reviewable.
Update: I amended the candidate fix commit message to English for the upstream branch.
Solution: openai/codex #24369 — NUL Bytes in function_call.name Break Session Resume
Issue
GitHub: openai/codex #24369
Title: Resume fails with Responses API 400 when persisted function_call.name contains NUL bytes
Labels: bug, CLI, tool-calls, session
Competition: Zero (0 comments)
Severity: High — Session corruption, unrecoverable without manual intervention
Quote: $2,000–$3,000
---
Root Cause Analysis
Symptom
A Codex CLI session becomes impossible to resume because the persisted transcript contains a
function_call.namewith embedded NUL bytes (\x00). When Codex replays that history to the Responses API, the API rejects with 400:Key Evidence from Issue
``
json
``{
"type": "response_item",
"payload": {
"type": "function_call",
"name": "apply\\^@\\^@\\^@\\^@\\^@\\^@",
"call_id": "call_EizYvzgC5R9tmVUJj17m5aDA"
}
}
apply\^@\^@\^@\^@\^@\^@→apply_patchand session resumedTechnical Root Cause
The NUL bytes (
\x00, displayed as\^@) in the persistedfunction_call.nameindicate memory corruption during the tool call serialization process. The most likely causes:The
argumentsfield also contains\^@sequences, confirming this is a systemic corruption of the entire function_call payload, not just the name field.Proposed Fix
Fix 1: Sanitize function_call.name Before API Replay
Fix 2: Sanitize Arguments Field
Fix 3: Pre-Replay Validation Pipeline
Fix 4: Prevention — Sanitize at Write Time
Testing Strategy
sanitizeFunctionCallNamecorrectly strips NUL bytes fromapply\^@\^@→applywriteFunctionCallrejects invalid tool namesImpact
---
Deliverables
transcript-sanitizer.ts— sanitization functions for name and argumentsreplay-validator.ts— pre-replay validation pipelinetranscript-writer.ts— write-time preventionI shipped Codex Rescue 0.1.0a3 with a regression based on this report. It now diagnoses corrupted persisted tool-call names, preserves the original rollout, and does not guess or rewrite the tool name.
If you still have the backed-up affected session, would you be willing to run
codex-rescue doctor --latestand share only sanitized output? No raw rollout or private data needed.https://github.com/shleder/codex-rescue
Nice manual recovery — you found the exact failure class: a corrupted persisted
function_callwhosename(andarguments) contain NUL/control bytes, so when Codex replays the transcript the Responses API rejects it against the^[a-zA-Z0-9_-]+$pattern. The session is otherwise intact; it's one poisoned record blocking the whole replay.The general version of your fix is: identify every record that fails the API's validation pattern, then either repair or drop just those records while keeping the durable transcript. Doing it by hand works for one record but doesn't scale if the corruption spans several tool calls, and editing the live JSONL in place risks introducing new malformations.
Full disclosure: I maintain codex-rescue (https://github.com/shleder/codex-rescue).
codex-rescue doctor --latestdetects malformed records including control-character corruption in tool-call fields, andsalvageextracts the durable history into a clean fork, dropping the poisoned items read-only-first so the original is never mutated. It automates exactly the backup-then-repair you did by hand, andverifychecks the salvaged session parses cleanly before you continue on it.