`app-server` drops global `-c` overrides when another `-c` appears after the subcommand

Open 💬 1 comment Opened Aug 17, 2026 by tarik02

What version of Codex CLI is running?

codex-cli 0.147.0

What subscription do you have?

ChatGPT Pro

Which model were you using?

Not applicable. The reproduction only calls experimentalFeature/list and does not start a turn.

What platform is your computer?

Linux 6.18.44 x86_64 unknown (NixOS)

What terminal emulator and version are you using (if applicable)?

Ghostty 1.3.1

Codex doctor report

Not included. The reproduction uses an empty temporary CODEX_HOME, does not require authentication, and does not start a model turn.

What issue are you seeing?

codex app-server silently drops every global -c / --config override placed before the app-server subcommand when any -c is also placed after the subcommand.

This works:

codex -c 'features.default_mode_request_user_input=true' app-server

experimentalFeature/list reports:

{
  "name": "default_mode_request_user_input",
  "enabled": true,
  "defaultEnabled": false
}

Adding an unrelated override after app-server causes the earlier override to disappear:

codex -c 'features.default_mode_request_user_input=true' app-server \
  -c 'suppress_unstable_features_warning=true'

The same request now reports:

{
  "name": "default_mode_request_user_input",
  "enabled": false,
  "defaultEnabled": false
}

This affects more than feature flags. Settings such as model_provider, custom model_providers, model defaults, approval policy, and sandbox mode can all be silently discarded.

The documented configuration precedence says CLI flags and --config overrides have the highest precedence: https://learn.chatgpt.com/docs/config-file/config-basic#configuration-precedence

What steps can reproduce the bug?

  1. Create a clean Codex home:

``bash
export CODEX_HOME="$(mktemp -d)"
``

  1. Start app-server with only a global override:

``bash
codex -c 'features.default_mode_request_user_input=true' app-server
``

  1. Send these JSON-RPC messages over stdin, waiting for the initialize response before sending initialized:

``json
{"id":1,"method":"initialize","params":{"clientInfo":{"name":"config-repro","version":"0"},"capabilities":{"experimentalApi":true}}}
{"method":"initialized"}
{"id":2,"method":"experimentalFeature/list","params":{"cursor":null,"limit":null,"threadId":null}}
``

  1. Find default_mode_request_user_input in response id: 2. It has enabled: true.
  1. Stop app-server and repeat with an unrelated post-subcommand override:

``bash
codex -c 'features.default_mode_request_user_input=true' app-server \
-c 'suppress_unstable_features_warning=true'
``

  1. Send the same JSON-RPC messages. default_mode_request_user_input now has enabled: false.

What is the expected behavior?

Codex should retain both sets of overrides in CLI order. The second command should apply both:

features.default_mode_request_user_input=true
suppress_unstable_features_warning=true

At minimum, Codex should not silently discard valid overrides based on whether they appear before or after the subcommand.

Additional information

Moving every override after app-server works around the bug:

codex app-server \
  -c 'features.default_mode_request_user_input=true' \
  -c 'suppress_unstable_features_warning=true'

Possibly related to #26436, which added app-server -c support, but that PR does not cover mixing overrides before and after the subcommand.

View original on GitHub ↗

1 Comment

jdcodes1 · 10 days ago

Traced on main @ 1f41cc5d92 — app-server is missing the root/subcommand override merge that every other codex subcommand performs, and the drop you measured is the documented clap behavior for multi-value global args falling into that gap.

The in-repo convention. -c/--config is declared global = true (utils/cli/src/config_override.rs), and subcommands that accept it flatten their own CliConfigOverrides and then explicitly merge the root-level flags in front with prepend_config_flags / prepend_root_overrides ("Prepend root-level config flags so they have lower precedence than command-specific flags", config_override.rs#L40-L45). main.rs does this in ~16 dispatch arms (tui, exec, mcp, plugin, …).

The app-server arm doesn't. AppServerCommand has no flattened config_overrides field at all (cli/src/main.rs#L525-L541), and its dispatch arm passes the root-derived root_config_overrides straight into run_main_with_transport_options with no merge (#L1131-L1185). So post-subcommand -c occurrences are captured by clap's global-arg propagation into the subcommand's match scope — and clap does not merge multi-value global args across levels (long-standing behavior; see clap-rs/clap#3959): the scope that "wins" supplies the whole list. That's exactly your observation — one -c after app-server and the pre-subcommand overrides vanish wholesale, with no warning, because from clap's perspective nothing was dropped.

Fix. Bring app-server in line with the existing pattern:

  1. add #[command(flatten)] config_overrides: CliConfigOverrides to AppServerCommand,
  2. in the dispatch arm, prepend_config_flags(&mut app_server_cli.config_overrides, root_config_overrides) like the other arms, and pass the merged set onward.

That preserves the documented precedence (root flags weaker than post-subcommand flags) and makes both your invocations equivalent.

Worth a quick audit while fixing: any other subcommand that accepts trailing -c without its own flattened CliConfigOverrides + prepend call has the same silent-drop shape — the pattern being convention rather than enforced makes this easy to reintroduce. A small regression test that runs the parser with -c a=1 <subcmd> -c b=2 and asserts both keys survive, parameterized over subcommands, would lock the convention in.