Windows users running agent CLIs inside WSL were landing on the native Windows guide and copying the README MCP command directly, which left Linux /usr/bin/od shadowing, daemon origin, Node ABI, and Codex config parse failures unexplained. Add a WSL2 guide and teach the Codex config normalizer to drop nested feature tables that current Codex parses as maps instead of boolean flags.
Constraint: WSL agent CLIs need the same Linux environment for the wrapper, daemon, Node modules, and credentials
Rejected: Docs-only workaround | would leave daemon-launched Codex runs failing on nested features tables
Confidence: high
Scope-risk: narrow
Directive: Keep WSL-specific guidance separate from native Windows PowerShell troubleshooting
Tested: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/codex-config-normalize.test.ts
Tested: pnpm --filter @open-design/daemon typecheck
Tested: pnpm guard
Tested: git diff --check
Not-tested: Manual WSL2 end-to-end MCP install on a Windows host
Related: Fixes#4648
The Codex CLI exits on config load when `service_tier` is not in {fast,flex}
("unknown variant '<value>', expected 'fast' or 'flex'"). The normalizer kept
a hardcoded map of known-stale values ({priority, default}) -> "fast"; every
new invalid value the Codex app writes then slipped through and crashed the CLI
until someone added it to the map. Production telemetry confirms this is live:
after `default` was added (#4477), the service_tier crash class persisted on
current versions (0.11.0) -- i.e. some other value is still leaking.
Replace the stale-map strategy with a wildcard: any service_tier value outside
{fast,flex} has its whole line removed, so the Codex CLI falls back to its
built-in default tier. Removing -- rather than forcing "fast" -- is the
smallest assumption: it never imposes an opinionated tier, and unknown/"default"
values mean "let the system choose" anyway. This ends the whack-a-mole.
Scope: this fixes the service_tier subset of the `agent_config_invalid`
telemetry bucket only. That bucket also classifies `default_permissions` and
duplicate-key config errors (run-failure-classification.ts), which are NOT
addressed here and remain follow-ups.
Cross-version: the normalizer runs unconditionally before every Codex spawn, so
removing an invalid value lets ANY installed Codex version fall back to its
default (robust downward). The valid set {fast,flex} is hardcoded and matches
every observed version (the CLI's own error names exactly that set); the only
residual risk is a future Codex version ADDING a new valid tier, which would be
wrongly stripped -- low, since Codex history has shrunk the set (priority->fast,
default dropped), and remove-only loses less than the old map-to-fast there.
- normalizeCodexConfigContent: line-anchored regex now captures the value +
line terminator and removes the line when the value is invalid (preserves
indentation, inline comments untouched on other keys, CRLF preserved).
- Updated the red spec to assert removal (incl. a previously-unhandled "turbo"
value that reproduces the production crash) and the media-path test that
asserted the old map-to-fast behavior.
- server.ts spawn comment updated to describe remove-only.
Refs #3408 (Slice 1 / fix_config), #4276, #4477.
* fix(daemon): normalize stale service_tier=priority in ~/.codex/config.toml before Codex launch
Codex CLI renamed service_tier="priority" to "fast" in a recent release.
The Codex app's own fast-mode toggle still writes the old value on some
installations, causing the CLI to exit with:
Error loading config.toml: unknown variant 'priority', expected 'fast'
or 'flex' in `service_tier`
The CLI parses config.toml before processing any -c overrides, so the
only fix is to patch the file on disk. Before spawning the Codex agent,
the daemon now calls normalizeCodexConfigFile() which replaces any stale
service_tier="priority" with service_tier="fast" in-place, leaving all
other config content intact. Errors (missing file, permission denied) are
silently swallowed so a read-only or absent config.toml never blocks
a launch.
fixes#4276
* fix(codex-config-normalize): line-anchor regex + atomic write + logged errors
Fixes two code-review blockers on the #4276 normalization module:
BLOCKER 1 — regex not line-anchored (data corruption):
Replace the global /\bservice_tier\s*=.../ pattern with a multiline-anchored
pattern /^(\s*)service_tier(\s*=\s*)(["'])priority\3(\s*(?:#.*)?)$/gm.
The new pattern only matches a standalone key line at the start of a line,
so "priority" appearing inside another key's value or inside a comment is
never rewritten. Indentation, original = spacing, and trailing inline comments
are preserved via capture groups.
BLOCKER 2 — unsafe in-place overwrite + swallowed errors:
Replace direct writeFile(configPath) with atomic temp-file + rename:
writeFile(configPath.<hex>.tmp) → rename(tmp, configPath)
Same-directory temp ensures same filesystem so rename is atomic. Write/rename
failures are now logged via console.warn (matching the daemon pattern) instead
of a bare catch {}. An injectable CodexConfigIO interface is exported so tests
can stub the rename to exercise the failure path without filesystem tricks.
Temp file cleanup is attempted on failure; secondary cleanup errors are ignored.
Tests: 11 new regression cases added to codex-config-normalize.test.ts:
- BLOCKER 1: priority in unrelated value/comment NOT rewritten; trailing
inline comment preserved; indentation preserved; flex/fast untouched;
real key coerced even when priority appears elsewhere in the file.
- BLOCKER 2: no temp-file litter on success; full content correct after
atomic write; rename failure logged and does not throw; unlink attempted.
* fix(codex): pass merged spawn env to normalizeCodexConfigFile (#4276)
The call site in server.ts was passing bare `process.env` to
`normalizeCodexConfigFile`. If a user configured CODEX_HOME via
Open Design's agentCliEnv.codex, the normalizer resolved the wrong
path (~/.codex/config.toml) and left the real config at the user's
CODEX_HOME with a stale service_tier="priority" value untouched.
Fix: pass the same merged env the Codex child launches with —
`{...process.env, ...(def.env||{}), ...configuredAgentEnv}` — so
resolveCodexConfigPath sees the same CODEX_HOME the child will see.
Also adds:
- FIX 1 regression tests: positive case (CODEX_HOME in merged env is
used) and negative case (without CODEX_HOME, default path is used)
- FIX 2 CRLF regression test: config.toml with CRLF line endings has
service_tier coerced to "fast" with all \r\n sequences preserved
FIX 3 (logging): console.warn at codex-config-normalize.ts:138 is
left as-is — no structured logger is trivially in scope at the call
site without extending the CodexConfigIO injection pattern, and the
reviewer confirmed console.warn already matches daemon conventions.
* fix(codex): expand tilde in CODEX_HOME before normalizing config path (#4276)
resolveCodexConfigPath now calls expandHomePath on the CODEX_HOME env value
before passing it to path.join. Without this, a user-configured
CODEX_HOME="~/.codex-alt" resolved to the literal path
"~/.codex-alt/config.toml" in the normalizer while the Codex child process
(via spawnEnvForAgent → expandConfiguredEnv) resolved it to the absolute
"<homedir>/.codex-alt/config.toml". The stale service_tier="priority" config
at the real expanded path was therefore never found and never patched.
Also routes the server.ts normalizer call through spawnEnvForAgent('codex', …)
instead of a hand-rolled env merge, mirroring the diagnostics-export.ts
`envFor('codex')` pattern to permanently eliminate drift between the env the
normalizer sees and the env the Codex child actually receives.
Adds two regression tests for the tilde case (RED before this fix, GREEN
after): one unit-level assertion that resolveCodexConfigPath expands "~/" to
an absolute path, and one end-to-end assertion that a stale config at the
expanded location is patched when CODEX_HOME contains a tilde prefix.
Refs #4276