Skip to content

fix(codexcli-mcp): strip empty env tables from generated config#1622

Merged
dyoshikawa merged 2 commits into
dyoshikawa:mainfrom
sirmacik:fix/codexcli-mcp-empty-env
May 12, 2026
Merged

fix(codexcli-mcp): strip empty env tables from generated config#1622
dyoshikawa merged 2 commits into
dyoshikawa:mainfrom
sirmacik:fix/codexcli-mcp-empty-env

Conversation

@sirmacik
Copy link
Copy Markdown
Contributor

@sirmacik sirmacik commented May 11, 2026

Summary

The codex generator currently emits an empty [mcp_servers.X.env] header for any server whose env is {}. Codex CLI 0.130+ rejects this on remote (sse/http/streamable_http) transports with:

Error loading config.toml: env is not supported for streamable_http
in `mcp_servers.X`

This is a hard startup failure for any source mcp.json that has a remote MCP server with an empty env (intentionally, or left over from another tool's schema shape).

Repro

// .rulesync/mcp.json
{
  "mcpServers": {
    "aws-knowledge": {
      "type": "sse",
      "url": "https://knowledge-mcp.global.api.aws",
      "env": {}
    }
  }
}

After rulesync generate -g:

[mcp_servers.aws-knowledge]
type = "sse"
url = "https://knowledge-mcp.global.api.aws"

[mcp_servers.aws-knowledge.env]   ← codex rejects this

Fix

Make removeEmptyEntries in codexcli-mcp.ts recursive so empty nested objects (env: {}) are stripped at any depth, not just at the top level. Arrays are preserved verbatim because an empty array can be a meaningful explicit override.

Side effect: stdio servers also get cleaner output (no more inert empty [mcp_servers.X.env] headers). Codex tolerates those, so this is purely cosmetic for stdio.

Tests added

  • should strip empty env object from remote (sse) server — repro test
  • should strip empty env object from stdio server (clean output)
  • should preserve populated env table on stdio server

All 54 codex MCP tests pass. pnpm cicheck is clean.

Risk

Minimal — recursing into nested objects is a generalization of the existing behaviour (which already strips empty top-level server configs). No existing test relied on empty env: {} being preserved inside a server config.

The codex CLI rejects empty `[mcp_servers.X.env]` headers on remote
(sse/http/streamable_http) transports with:
  "env is not supported for streamable_http"

When a rulesync source has `"env": {}` on a server (intentionally or
left over from another tool's shape), the codex generator passes it
through and smol-toml serialises it as an empty TOML table — which
breaks codex 0.130+ on startup.

Make `removeEmptyEntries` recursive so empty nested objects (like an
empty `env`) are stripped at any depth. Arrays are preserved verbatim
since an empty array can be a meaningful explicit override.

This also cleans up codex output for stdio servers (no more empty
`[mcp_servers.X.env]` headers when no env vars are needed) — codex
tolerates them there, but they were always functionally inert.

Tests cover:
- empty `env` on sse server is stripped, file content has no `.env]` header
- empty `env` on stdio server is stripped
- populated `env` table is preserved
Self-review polish before marking dyoshikawa#1622 ready:

1. Parameterize the remote-transport test over all three transports
   (sse, http, streamable_http) that codex CLI rejects empty env on.
   Previously only `sse` was tested, leaving `http` and
   `streamable_http` susceptible to silent regression if the strip
   logic is ever made transport-aware.

2. Remove the brittle `expect(...).not.toContain('.env]')` file-content
   assertion. The structural `expect(server?.env).toBeUndefined()`
   check is sufficient and is not vulnerable to false-pass on unrelated
   server names containing `.env` (or false-fail on benign formatting
   changes).

3. Add defensive assertions that non-env fields (`type`, `url`,
   `command`, `args`) survive the strip in all three test cases.
   Guards against a future over-aggressive strip that accidentally
   drops other server fields.

No functional change to the source code.
@sirmacik sirmacik marked this pull request as ready for review May 11, 2026 20:49
@dyoshikawa dyoshikawa merged commit b3d5ea2 into dyoshikawa:main May 12, 2026
6 checks passed
@dyoshikawa
Copy link
Copy Markdown
Owner

@sirmacik Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants