fix(cli): escape env values in the codex config.toml writer - #34
Open
stephanbrez wants to merge 1 commit into
Open
fix(cli): escape env values in the codex config.toml writer#34stephanbrez wants to merge 1 commit into
stephanbrez wants to merge 1 commit into
Conversation
`_write_bootstrap_config` interpolated every `[mcp_servers.zenith.env]`
value into a TOML basic string with a raw f-string:
env_lines = "\n".join(f'{k} = "{v}"' for k, v in env.items())
ACP commands splice codex config via `-c key="value"`, so a role's
command can carry double quotes. Interpolated raw, the TOML string
terminates at the first inner quote:
ZENITH_VALIDATOR_ACP_COMMAND = "codex-acp -c model="gpt-5.6-terra""
which is invalid TOML — codex fails to parse config.toml or drops the
managed block, and the role silently falls back to defaults. The same
hole affects any forwarded allowlisted env value containing a quote or
backslash.
Serialize with json.dumps instead: TOML basic strings share JSON's
escape syntax, and json.dumps emits only escapes TOML accepts. This
mirrors the `args = {json.dumps(server_args)}` line directly below, and
covers every role in one place since the loop spans the whole env dict.
Env var names are valid TOML bare keys, so keys are unchanged.
Only the `codex_config` branch was affected. The `mcp_json` branch hands
the same env dict to json.dumps and has always been correct.
Regression test drives `zenith init --agent codex` with a distinct
quoted command for worker and validator and asserts config.toml parses
and each value round-trips byte-identical; it raises TOMLDecodeError
without the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
stephanbrez
added a commit
to stephanbrez/zenith
that referenced
this pull request
Jul 28, 2026
Filed as PR Intelligent-Internet#34 from upstream/toml-env-escaping. Records that the Intelligent-Internet#31 relationship is motivational only, so the row is not misread as depends-on-unmerged-work like 02aaf73. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
zenith init --agent codexwrites[mcp_servers.zenith.env]into.codex/config.tomlby interpolating each value into a TOML basic stringwith a raw f-string (
cli.py, no escaping of",\, or newlines):-c key="value"is the supported way to splice codex config throughZENITH_*_ACP_COMMAND, so those values routinely contain double quotes:emits invalid TOML — the string terminates at
model=, leaving a danglinggpt-5.6-terra"":Codex then fails to parse
config.tomlor drops the whole managed block, andthe role silently falls back to defaults. Because the line loops over the
entire env dict, the same hole affects every role's ACP command and any
forwarded allowlisted value (
ANTHROPIC_MODEL,ZAI_API_KEY, base URLs)that contains a quote or backslash.
Fix
Serialize with
json.dumps. TOML basic strings share JSON's escape syntax,and
json.dumpsemits only escapes TOML accepts (\",\\,\n,\t,\uXXXX; never\/by default). This mirrors theargs = {json.dumps(server_args)}line directly below it, and fixes everyrole at once since the loop spans the whole env dict. Env var names are valid
TOML bare keys, so keys are unchanged.
Only the
codex_configbranch was affected — themcp_jsonbranch hands thesame env dict to
json.dumpsand has always been correct.Why this surfaced now
Related: #31. Before that change, a custom
-c model="..."insideZENITH_*_ACP_COMMANDwas silently discarded by the npmcodex-acpadapter,so there was little reason to pass a quoted
-cvalue to--worker-acp-command. #31 makes those overrides functional by routing themthrough
CODEX_CONFIG, which turns this latent escaping hole into reachableconfig corruption.
This PR has no code dependency on #31. It applies cleanly to
mainas-is,does not touch
acp_runner.py, and can merge before, after, or independentlyof that PR. (
_augment_acp_commandhas always appended-c sandbox_mode="danger-full-access", but that happens at dispatch and neverreaches
config.toml; only user-supplied commands are written byzenith init.)Testing
New
test_codex_init_escapes_quoted_acp_commandsdriveszenith init --agent codexwith a distinct quoted command for worker and validator, assertsconfig.tomlparses viatomllib, and asserts each value round-tripsbyte-identical. It raises
TOMLDecodeErrorwithout the fix.Existing codex config tests are unaffected —
json.dumpson quote-free valuesproduces byte-identical output to the current code.
uv run pytest -q— 213 passed, 7 skippeduv run ruff check .— passeduv run mypy src— passedtomllib.loadson the generatedconfig.tomlraises; after, all commands parse with quotes intact.🤖 Generated with Claude Code