Client or integration
Codex CLI (ocx) — routing policy profiles, and the config set / config import commands.
Area
CLI
Summary
Two independent config-shells prompts reported separately, now combined. Both share the theme "an option I set is not honored / silently loses my data," but they are two distinct defects:
-
Routing optimize.latency does not optimize latency. Setting optimize.latency on a routing profile does not make the router prefer faster providers. On the live policy evaluator (evaluatePolicyProfile), optimize.latency produces no measured-latency score term; with optimize: { latency: 1, health: 0, cost: 0, quota: 0 } selection is a pure argmax over declaration order — reversing the provider list flips the winner to the 40 s-latency provider. Measured latency only ever rides inside the health score (scaled by optimize.health), never optimize.latency. The reference docs call latency a "residual priority split," so this is a design/docs gap rather than a crash: the option name misleads operators, and the residual 1 - health - quota - cost formula is not honored when quota/cost evidence is absent.
-
ocx config import / set silently deletes hand edits. A hand-edit committed to ~/.opencodex/config.json is destroyed, deterministically, by ocx config import file.json --yes when the import file lacks the edited field — import reads only the file passed (src/cli/config-command.ts:178, loadInput at :67), never the current config, and writes the whole thing. ocx config set/unset additionally have a lost-update race (snapshot read at config-command.ts:133, whole-object write at :145); reproduced 34/51 stale reverts under a concurrent writer.
I'm filing both in one issue as the parent asked. I'm not asserting a confirmed root cause from logic alone; the Evidence section lists what was actually run.
Reproduction
Routing latency (run the real evaluator)
Config: profile "lat" with candidates [a/m1, b/m1], optimize { latency:1, health:0, cost:0, quota:0 }, evidence a/m1 recentLatencyMs 800 (fast), b/m1 40_000 (slow).
Order [fast, slow] -> wins a (fast): a total=1.000 comps={"configuredPriority":1} b total=0.500
Order [slow, fast] -> wins b (slow): b total=1.000 comps={"configuredPriority":1} a total=0.500
Reversing the candidate list flips the winner to the 40 s provider; the only score component is configuredPriority. No latency term ever appears.
Config import clobber (deterministic, no concurrency)
ocx config set emptyCompletionRetry true # writes ~/.opencodex/config.json
# hand-edit: add "streamMode":"eager-relay", "customFlag":"x" ; set emptyCompletionRetry=false
ocx config import ./fresh.json --yes # fresh.json omits those two fields
ocx config get streamMode # -> error: config path not found: streamMode (edit destroyed)
ocx config get customFlag # -> error: config path not found: customFlag
ocx config get emptyCompletionRetry # -> true (hand-toggle reverted)
Config set race (with concurrent writer)
ocx config set port 10100 in a loop while a separate process rewrites an unrelated field; live result: 34 of 51 rounds the concurrent counter reverted to a stale lower value.
Version
2.22.0 (main d9de895, @bitkyc08/opencodex)
Operating system
Linux
Provider and model
OpenAI Codex Responses API (routing case); config case is provider-independent.
Logs or error output
No crash/error is raised — both defects are silent (wrong selection; field disappears). The routing case is visible in the evaluator output above; the config case has no explicit error.
Redacted configuration
// routing profile
{ "routingProfiles": { "profiles": [{ "name": "lat", "optimize": { "latency": 1 } }] } }
// config edit before import (the part that is destroyed)
{ "streamMode": "eager-relay", "customFlag": "x", "emptyCompletionRetry": true }
Evidence
- Routing: live
bun test tests/latency-scope-live.test.ts against real evaluatePolicyProfile → 5 pass / 0 fail (fast-vs-slow order flip, no latency component ever emitted). grep -c "latency" src/routing/evaluator.ts → 0; optimize.latency has no consumer outside profile.ts (src/routing/evaluator.ts:398-418). Measured p50 latency folds only into healthScore (src/routing/health.ts:386-389), scaled by optimize.health: (evaluator.ts:408), not optimize.latency.
- Config: live CLI in throwaway copy (Bun 1.3.14, source v2.22.0 d9de895): hand-edit →
config import fresh.json --yes (schema-valid, omitting the fields) → exit 0, edits gone; config set port 10100 under concurrent writer → 34/51 counter reverts. Source: src/cli/config-command.ts:133,145,178; src/config.ts:2766 (saveConfig does no general disk rebase), :3185 (saveConfigPreservingClaudeCode — the rebasing writer, used by server routes, not these CLI commands); src/server/index.ts:562 (baseline armed at server start).
import is not a blind overwrite: it validates the file against the schema and aborts on invalid input (a file missing providers wrote nothing). Data loss occurs with a schema-valid-but-partial file, which is the common case.
Expected behavior
optimize.latency (if intended as a latency knob) should consult a real latency signal and prefer the lower-p50 provider regardless of order; otherwise the docs/edit schema should make clear it is a residual priority split and the residual should match the documented 1 - health - quota - cost even when evidence is missing.
ocx config set/unset should merge onto the current on-disk config before writing (like the server-side rebasing writer), and ocx config import should diff its candidate against the current on-disk config and warn about keys it is about to remove.
Checks
Client or integration
Codex CLI (
ocx) — routing policy profiles, and theconfig set/config importcommands.Area
CLI
Summary
Two independent config-shells prompts reported separately, now combined. Both share the theme "an option I set is not honored / silently loses my data," but they are two distinct defects:
Routing
optimize.latencydoes not optimize latency. Settingoptimize.latencyon a routing profile does not make the router prefer faster providers. On the live policy evaluator (evaluatePolicyProfile),optimize.latencyproduces no measured-latency score term; withoptimize: { latency: 1, health: 0, cost: 0, quota: 0 }selection is a pure argmax over declaration order — reversing the provider list flips the winner to the 40 s-latency provider. Measured latency only ever rides inside thehealthscore (scaled byoptimize.health), neveroptimize.latency. The reference docs calllatencya "residual priority split," so this is a design/docs gap rather than a crash: the option name misleads operators, and the residual1 - health - quota - costformula is not honored when quota/cost evidence is absent.ocx config import/setsilently deletes hand edits. A hand-edit committed to~/.opencodex/config.jsonis destroyed, deterministically, byocx config import file.json --yeswhen the import file lacks the edited field — import reads only the file passed (src/cli/config-command.ts:178,loadInputat:67), never the current config, and writes the whole thing.ocx config set/unsetadditionally have a lost-update race (snapshot read atconfig-command.ts:133, whole-object write at:145); reproduced 34/51 stale reverts under a concurrent writer.I'm filing both in one issue as the parent asked. I'm not asserting a confirmed root cause from logic alone; the Evidence section lists what was actually run.
Reproduction
Routing latency (run the real evaluator)
Config: profile "lat" with candidates
[a/m1, b/m1],optimize { latency:1, health:0, cost:0, quota:0 }, evidencea/m1recentLatencyMs 800 (fast),b/m140_000 (slow).Reversing the candidate list flips the winner to the 40 s provider; the only score component is
configuredPriority. No latency term ever appears.Config import clobber (deterministic, no concurrency)
Config set race (with concurrent writer)
ocx config set port 10100in a loop while a separate process rewrites an unrelated field; live result: 34 of 51 rounds the concurrent counter reverted to a stale lower value.Version
2.22.0 (main d9de895,
@bitkyc08/opencodex)Operating system
Linux
Provider and model
OpenAI Codex Responses API (routing case); config case is provider-independent.
Logs or error output
No crash/error is raised — both defects are silent (wrong selection; field disappears). The routing case is visible in the evaluator output above; the config case has no explicit error.
Redacted configuration
Evidence
bun test tests/latency-scope-live.test.tsagainst realevaluatePolicyProfile→ 5 pass / 0 fail (fast-vs-slow order flip, nolatencycomponent ever emitted).grep -c "latency" src/routing/evaluator.ts→ 0;optimize.latencyhas no consumer outsideprofile.ts(src/routing/evaluator.ts:398-418). Measured p50 latency folds only intohealthScore(src/routing/health.ts:386-389), scaled byoptimize.health: (evaluator.ts:408), notoptimize.latency.config import fresh.json --yes(schema-valid, omitting the fields) → exit 0, edits gone;config set port 10100under concurrent writer → 34/51 counter reverts. Source:src/cli/config-command.ts:133,145,178;src/config.ts:2766(saveConfigdoes no general disk rebase),:3185(saveConfigPreservingClaudeCode— the rebasing writer, used by server routes, not these CLI commands);src/server/index.ts:562(baseline armed at server start).importis not a blind overwrite: it validates the file against the schema and aborts on invalid input (a file missingproviderswrote nothing). Data loss occurs with a schema-valid-but-partial file, which is the common case.Expected behavior
optimize.latency(if intended as a latency knob) should consult a real latency signal and prefer the lower-p50 provider regardless of order; otherwise the docs/edit schema should make clear it is a residual priority split and the residual should match the documented1 - health - quota - costeven when evidence is missing.ocx config set/unsetshould merge onto the current on-disk config before writing (like the server-side rebasing writer), andocx config importshould diff its candidate against the current on-disk config and warn about keys it is about to remove.Checks
optimize.latencyhas no latency score term and only becomes the residualpriorityWeight#1834/[Bug]ocx config set/ocx config importbypass the protected 3-way reconcile and can clobber concurrent disk / hand edits #1835/[Bug] Routing profileoptimize.latencyhas no latency score term and only becomes the residualpriorityWeight#1837/[Bug]ocx config set/ocx config importbypass the protected 3-way reconcile and can clobber concurrent disk / hand edits #1838 records).d9de895(v2.22.0).