Skip to content

fix(doctor): preserve explicit agentRuntime pin during codex model migration [AI-assisted]#84142

Closed
nxmxbbd wants to merge 2 commits into
openclaw:mainfrom
nxmxbbd:fix/84038-preserve-pi-runtime
Closed

fix(doctor): preserve explicit agentRuntime pin during codex model migration [AI-assisted]#84142
nxmxbbd wants to merge 2 commits into
openclaw:mainfrom
nxmxbbd:fix/84038-preserve-pi-runtime

Conversation

@nxmxbbd

@nxmxbbd nxmxbbd commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: openclaw doctor --fix silently overwrote an explicit agentRuntime: { id: "pi" } opt-out with { id: "codex" } while migrating legacy openai-codex/* model refs to canonical openai/* form, flipping the user from the PI runtime back onto the native Codex runtime (3-4× token inflation per request, per [Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation #84038).
  • Solution: Skip the default-codex pin synthesis on the canonical model entry when a legacy openai-codex/* form of that model ref already has an explicit non-default agentRuntime.id (anything other than auto / default / missing). rewriteModelsMap then carries the legacy pin forward through its existing legacy-wins fallback branch.
  • What changed: src/commands/doctor/shared/codex-route-warnings.ts (+24, -0). New helper legacyEntryHasExplicitNonDefaultRuntimePin(models, canonicalModelRef) plus one guard in ensureCodexRuntimePolicy before setModelRuntimePolicy.
  • What did NOT change (scope boundary): Session-store stale-pin clearing (repairCodexSessionStoreRoutes, clearStaleSessionRuntimePins) is untouched. Top-level agents.defaults.agentRuntime clearing (clearLegacyAgentRuntimePolicy) is untouched. The migration of model refs themselves (openai-codex/Xopenai/X) is unchanged. auto / default runtime pins remain overwritable.

Motivation

The native Codex runtime currently produces 3-4× the token usage of the PI runtime for the same GPT-5.x request (the upstream Codex issue OpenClaw cannot fix at the provider layer). Users who explicitly pin agentRuntime: { id: "pi" } to opt out of this need that opt-out to survive doctor --fix. Every doctor run today silently puts them back on the broken runtime.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Real behavior proof

  • Behavior or issue addressed: doctor --fix overwrites agents.defaults.models["openai-codex/gpt-5.4"].agentRuntime = { id: "pi" } with { id: "codex" } while migrating the legacy openai-codex/* model ref to openai/*, silently re-enrolling the user into the native Codex runtime they explicitly opted out of.

  • Real environment tested: Local fresh worktree off upstream/main f07c87405c on Linux 6.17.0 (Node 22.22.1, pnpm 11.1.0). Two sibling worktrees: baseline (upstream/main, no fix) at /tmp/openclaw-84038-baseline and fix branch at /tmp/openclaw-84038. Production maybeRepairCodexRoutes (the same function src/commands/doctor/repair-sequencing.ts:82 calls during openclaw doctor --fix) invoked directly against the exact 4-key user config from [Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation #84038.

  • Exact steps or command run after this patch:

    # In each worktree (baseline and fix branch):
    node --import tsx ./_proof-trace.mjs
    #   _proof-trace.mjs imports { maybeRepairCodexRoutes }
    #   from src/commands/doctor/shared/codex-route-warnings.ts and calls it
    #   with shouldRepair: true on the issue body's exact config.
  • Evidence after fix:

Terminal capture from this branch, copied live output:

=== INPUT CONFIG (user's #84038 setup) ===
  {
    "agents": {
      "defaults": {
        "model": { "primary": "openai-codex/gpt-5.4" },
        "models": {
          "openai-codex/gpt-5.4": { "agentRuntime": { "id": "pi" } }
        }
      }
    },
    "auth": { "order": { "openai-codex": ["openai-codex:user@example.com"] } },
    "plugins": { "entries": { "codex": { "enabled": false } } }
  }

  === DOCTOR CHANGES ===
  Repaired Codex model routes:
  - agents.defaults.model.primary: openai-codex/gpt-5.4 -> openai/gpt-5.4.
  - agents.defaults.models.openai-codex/gpt-5.4: openai-codex/gpt-5.4 -> openai/gpt-5.4.

  === POST-MIGRATION CONFIG (agents block) ===
  {
    "defaults": {
      "model": { "primary": "openai/gpt-5.4" },
      "models": {
        "openai/gpt-5.4": { "agentRuntime": { "id": "pi" } }
      }
    }
  }

agentRuntime preserved as { id: pi }?: YES
  • Observed result after fix: the legacy openai-codex/gpt-5.4 model ref is migrated to canonical openai/gpt-5.4, the corresponding map entry follows the rename, and the user's explicit agentRuntime: { id: "pi" } opt-out is preserved on the migrated entry. The previous "Set agents.defaults.models.openai/gpt-5.4.agentRuntime.id to 'codex'" line is correctly absent because no synthesized default needs setting, and plugins.entries.codex.enabled stays false as the user requested (no spurious auto-enable, because no route now claims the codex runtime).

  • What was not tested: live gateway runtime / actual provider request emission (the runtime resolution side is exercised by existing resolveAgentHarnessPolicy tests in codex-route-warnings.test.ts at L3000-3006 and L3036-3042 covering both pi and codex shapes), and the macOS install path the original reporter is on (the migration code path is platform-agnostic, so the issue surface should reproduce / fix on macOS the same way).

  • Before evidence (baseline, same script same input on upstream/main f07c87405c):

    === DOCTOR CHANGES (BEFORE FIX) ===
    Repaired Codex model routes:
    - agents.defaults.model.primary: openai-codex/gpt-5.4 -> openai/gpt-5.4.
    - agents.defaults.models.openai-codex/gpt-5.4: openai-codex/gpt-5.4 -> openai/gpt-5.4.
    Set agents.defaults.models.openai/gpt-5.4.agentRuntime.id to "codex" so repaired OpenAI refs keep Codex auth routing.
    Enabled plugins.entries.codex because configured agent routes use Codex runtime.
    
    === POST-MIGRATION CONFIG (agents block) ===
    {
      "defaults": {
        "model": { "primary": "openai/gpt-5.4" },
        "models": {
          "openai/gpt-5.4": { "agentRuntime": { "id": "codex" } }
        }
      }
    }
    
    agentRuntime preserved as { id: pi }?: NO (got {"id":"codex"})
    

Root Cause

  • Root cause: in rewriteAgentModelRefs, the AGENT_MODEL_CONFIG_KEYS loop processes model first and calls preserveCodexRuntimePolicyForNewHitsensureCodexRuntimePolicy(modelRef="openai/X") while the canonical entry models["openai/X"] does not yet exist. ensureCodexRuntimePolicy synthesizes { agentRuntime: { id: "codex" } } on a fresh empty entry. rewriteModelsMap then renames openai-codex/Xopenai/X, and its { ...legacyRecord, ...canonicalRecord } spread places the just-synthesized codex pin last, silently dropping the user's { id: "pi" }.
  • Missing detection / guardrail: ensureCodexRuntimePolicy only looked at the canonical entry's existing agentRuntime when deciding whether to synthesize. It did not consider that a legacy form of the same model ref in the same models map might carry an explicit non-default pin the user expected to keep through the migration.
  • Contributing context (if known): two existing tests in codex-route-warnings.test.ts already encode the maintainer intent to preserve such pins — "preserves explicit model-scoped runtime pins when repairing legacy model map keys" (L2974) for the models-map-only case, and "overwrites non-concrete model-scoped runtime pins when preserving Codex route intent" (L3009) for the auto/default case. The combined-config path (legacy ref present in BOTH model.primary and models[...]) was the previously uncovered seam.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/commands/doctor/shared/codex-route-warnings.test.ts
  • Scenario the test should lock in: user has agents.defaults.model.primary = "openai-codex/X" AND agents.defaults.models["openai-codex/X"].agentRuntime = { id: "pi" }. After maybeRepairCodexRoutes({ shouldRepair: true }), the migrated entry models["openai/X"] must have agentRuntime = { id: "pi" }, and models["openai-codex/X"] must be gone.
  • Why this is the smallest reliable guardrail: the bug is purely in the rewrite sequence inside rewriteAgentModelRefs; a unit-level config-in/config-out assertion against maybeRepairCodexRoutes exercises the full repair pipeline (model-slot rewrite, ensureCodexRuntimePolicy decision, rewriteModelsMap rename + merge) without needing a real gateway or provider auth wiring.
  • Existing test that already covers this (if any): No. L2974 is similar but covers the model.primary-absent variant which already passes today; the combined-config variant from [Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation #84038 was uncovered.
  • If no new test is added, why not: N/A — added as a new it(...) block (RED on the prior commit cd0f8f800e, GREEN on the fix commit 9d932635ae).

User-visible / Behavior Changes

  • openclaw doctor --fix no longer rewrites agentRuntime.id to "codex" on a canonical openai/X entry when a legacy openai-codex/X form of the same model ref carries an explicit non-default pin (e.g. { id: "pi" }). The legacy pin survives the rename.
  • As a direct downstream consequence, plugins.entries.codex is no longer auto-enabled in that scenario (the migrated route now claims the pi runtime, not codex, so enableCodexPluginForRequiredRoutes does not need to touch the plugin entry). This matches the user's stated intent in [Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation #84038 ("explicitly disabled the codex plugin"). Users who left agentRuntime unset, or set it to auto / default / codex, see no behavior change.

Diagram

Before fix:
[user config]
  model.primary = "openai-codex/X"
  models["openai-codex/X"].agentRuntime = { id: "pi" }
        |
        v
[AGENT_MODEL_CONFIG_KEYS loop processes "model"]
  -> rewrite model.primary to "openai/X" (hit added)
  -> preserveCodexRuntimePolicyForNewHits -> ensureCodexRuntimePolicy("openai/X")
     -> models["openai/X"] does not exist yet
     -> SYNTHESIZE models["openai/X"] = { agentRuntime: { id: "codex" } }
        |
        v
[rewriteModelsMap renames legacy key]
  -> legacyRecord  = { agentRuntime: { id: "pi" } }     # user's pin
  -> canonicalRecord = { agentRuntime: { id: "codex" } } # synthesized above
  -> merge spread { ...legacy, ...canonical }
     -> canonical wins -> { agentRuntime: { id: "codex" } }   # USER'S PIN LOST

After fix:
[same user config]
        |
        v
[AGENT_MODEL_CONFIG_KEYS loop processes "model"]
  -> rewrite model.primary to "openai/X" (hit added)
  -> preserveCodexRuntimePolicyForNewHits -> ensureCodexRuntimePolicy("openai/X")
     -> models["openai/X"] does not exist yet
     -> legacyEntryHasExplicitNonDefaultRuntimePin(models, "openai/X") = true
        (legacy form "openai-codex/X" has agentRuntime.id = "pi")
     -> EARLY RETURN, no synthesis
        |
        v
[rewriteModelsMap renames legacy key]
  -> legacyRecord  = { agentRuntime: { id: "pi" } }
  -> canonicalRecord = undefined   (nothing synthesized)
  -> falsy branch -> canonicalEntry ?? legacyEntry -> legacyEntry
     -> models["openai/X"] = { agentRuntime: { id: "pi" } }   # PIN PRESERVED

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

The patch is a local config-rewrite decision change. No auth, secret, capability, network, or filesystem surface changes.

Repro + Verification

Environment

Steps

  1. Check out upstream/main f07c87405c in worktree A and fix/84038-preserve-pi-runtime in worktree B.
  2. In each worktree, run pnpm install --prefer-offline.
  3. Drop the same _proof-trace.mjs into each worktree. The script imports maybeRepairCodexRoutes from src/commands/doctor/shared/codex-route-warnings.ts and calls it with shouldRepair: true on the issue's exact config.
  4. In each worktree, run node --import tsx ./_proof-trace.mjs and compare stdout.

Expected

  • After fix: models["openai/gpt-5.4"].agentRuntime = { id: "pi" }. No Set agents.defaults.models.openai/gpt-5.4.agentRuntime.id to "codex" line in changes. No Enabled plugins.entries.codex line.

Actual

  • After fix: matches expected (see Evidence above).
  • Before fix (baseline): models["openai/gpt-5.4"].agentRuntime = { id: "codex" }, with both the Set ... and Enabled ... lines present in changes.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Live-output evidence captured in the Real behavior proof section above. Repro test landed in src/commands/doctor/shared/codex-route-warnings.test.ts (it("preserves an explicit non-default agentRuntime pin on the legacy model entry during migration (#84038)", ...)). Lint, typecheck, and the full codex-route-warnings.test.ts + repair-sequencing.test.ts sweep all pass; these are supplemental to the live-output evidence.

Human Verification

  • Verified scenarios:
    • User's exact 4-key config from [Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation #84038 → migration runs, pin preserved (live node --import tsx output captured both directions).
    • Same config on upstream/main baseline → migration runs, pin overwritten (matches the user's reported regression).
    • Existing scenarios kept locked in: 92/92 in codex-route-warnings.test.ts, 10/10 in repair-sequencing.test.ts. Specifically L2974 ("preserves explicit model-scoped runtime pins when repairing legacy model map keys" — pi pin preserved when only models map is set, no model.primary) and L3009 ("overwrites non-concrete model-scoped runtime pins when preserving Codex route intent" — auto pin still gets overwritten by codex).
  • Edge cases checked:
    • agentRuntime: { id: "auto" } legacy pin → not preserved (still overwritten by codex synthesis; matches existing maintainer intent).
    • agentRuntime: { id: "default" } legacy pin → not preserved (same as auto).
    • No legacy agentRuntime at all → unchanged (codex synthesis still happens).
    • User has both legacy and canonical entries with explicit pins → canonical entry's pin already has explicit value, ensureCodexRuntimePolicy's existing pre-check returns early, no synthesis happens, rewriteModelsMap keeps canonical via its current spread order.
  • What you did not verify: live gateway / provider request emission (the runtime resolution is exercised by existing resolveAgentHarnessPolicy tests in the same file); the macOS install path the original reporter is on (the migration logic is platform-agnostic).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A. Users with no agentRuntime pin (the vast majority) see identical doctor behavior. Users on a non-default explicit pin keep it after upgrade — no manual reapply needed anymore.

Risks and Mitigations

  • Risk: a user who genuinely WANTED their agentRuntime: { id: "pi" } (or similar non-codex pin) on a legacy openai-codex/X ref to be REPLACED by { id: "codex" } during migration no longer gets that automatic replacement.
    • Mitigation: this is the bug, not a new risk. The previous behavior was silently incorrect — an explicit pin meant the user did not want default codex. Existing test "overwrites non-concrete model-scoped runtime pins when preserving Codex route intent" (L3009) keeps auto / default overwritable for users who want default-policy behavior. A user who actually wants codex can set { id: "codex" } explicitly, which the fix also leaves alone (existing ensureCodexRuntimePolicy early-exit on concrete non-default pin handles this case).
  • Risk: behavior change to plugins.entries.codex auto-enable. When the user's preserved pin is non-codex, the migrated route no longer claims the codex runtime, so the codex plugin is no longer auto-enabled.

AI-assisted disclosure

  • AI-assisted — diagnosis and fix authored with Claude Opus 4.7 in a NexCore agent session. Real-behavior proof above was captured by directly invoking the production maybeRepairCodexRoutes function from a Node script on the human-operator's machine using the exact user config from the issue body. Co-author trailer: Co-Authored-By: Nex <nex@dbitstec.com>.
  • I understand what the code does, the trigger sequence inside rewriteAgentModelRefs, why ensureCodexRuntimePolicy is the right place for the early-return, and the trade-off vs reordering rewriteModelsMap (a second considered approach, rejected because it shifts an existing snapshot test's expected changes[] ordering for no semantic gain).
  • Session log available on request; not attached to keep PR body focused.
  • codex review --base origin/main was not run because Codex CLI is not installed on the human-operator's local machine. Equivalent diff-review steps performed: typecheck (pnpm check:changed --base upstream/main, lane check-prod-types + check-test-types), lint (oxlint 0 warnings 0 errors on 8650 files / 217 rules), oxfmt --check clean, repro test exercises the exact behavior.
  • Will resolve bot review conversations after addressing them; will not leave bot threads dangling for maintainers.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 19, 2026
@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: passed.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR adds a doctor migration guard and regression test so legacy openai-codex/* model-map entries with explicit non-default agentRuntime pins keep that pin when migrated to canonical openai/* refs.

Reproducibility: yes. The current-main source path shows the synthesis-before-merge ordering, and the PR body supplies before/after terminal output invoking the production repair function on the reported config.

PR rating
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Summary: Focused, likely mergeable bug fix with sufficient terminal proof, regression coverage, and no blocking review findings.

Rank-up moves:

  • none
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

PR egg
✨ Hatched: 🥚 common Velvet Shellbean

        .--^^^^--.           
     .-'  o    o  '-.        
    /       \__/      \      
   |    /\  ____  /\   |     
   |   /  \/____\/  \  |     
    \  \_.------._/  /       
     '._  `----'  _.'        
        '-.____.-'           
       _/|_|  |_|\_          
      /__|      |__\         
       .-----------.         
      '-------------'        

Rarity: 🥚 common.
Trait: sparkles near resolved comments.
Image traits: location review cove; accessory little merge flag; palette cobalt, lime, and pearl; mood mischievous; pose pointing at a small proof artifact; shell soft speckled shell; lighting golden review-room light; background small review tokens.
How to hatch it: once this PR reaches status: 👀 ready for maintainer look or status: 🚀 automerge armed, the PR author or a maintainer can comment @clawsweeper hatch to turn this ASCII egg into its generated creature image.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Velvet Shellbean in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchable usually means sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

Real behavior proof
Sufficient (terminal): The PR body includes copied live terminal before/after output against the production repair function and reported config, showing the runtime pin preserved after the patch.

Risk before merge
Why this matters: - Exact-head required checks for 6952ea7b650a9343f67bda6306fc619002f49706 were still pending or queued at review time.

  • The proof covers doctor config mutation, not a live gateway/provider request; downstream runtime resolution is covered by existing unit tests rather than live transport proof.

Maintainer options:

  1. Decide the mitigation before merge
    Land this focused doctor migration fix after exact-head required checks pass, while keeping the broader native Codex token-usage policy separate from this config-preservation repair.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge
No repair lane is needed; the remaining action is to let exact-head checks and the existing automerge/maintainer gate finish.

Security
Cleared: The diff only changes local doctor config rewrite logic and a regression test; it adds no dependency, network, secret, CI, or code-execution surface.

Review details

Best possible solution:

Land this focused doctor migration fix after exact-head required checks pass, while keeping the broader native Codex token-usage policy separate from this config-preservation repair.

Do we have a high-confidence way to reproduce the issue?

Yes. The current-main source path shows the synthesis-before-merge ordering, and the PR body supplies before/after terminal output invoking the production repair function on the reported config.

Is this the best way to solve the issue?

Yes. The guard is narrowly placed at the Codex runtime synthesis point, preserves explicit non-default pins, and leaves existing auto/default behavior intact without reordering the broader migration pipeline.

Label justifications:

  • P1: The PR fixes a user-facing doctor regression that can silently change model runtime and auth-provider routing for existing users.

What I checked:

  • Current main rewrite order reproduces the bug path: Current main rewrites agents.defaults.model before rewriting the model map, calls ensureCodexRuntimePolicy for the canonical hit, and later merges canonical model-map records over legacy records, which can let a synthesized Codex pin overwrite a legacy PI pin. (src/commands/doctor/shared/codex-route-warnings.ts:1522, 5c9a8f33b312)
  • Patch guard targets the implicated synthesis point: The PR adds legacyEntryHasExplicitNonDefaultRuntimePin and returns before setModelRuntimePolicy when a legacy model-map entry for the same canonical ref carries a concrete non-default runtime pin. (src/commands/doctor/shared/codex-route-warnings.ts:1997, 6952ea7b650a)
  • Regression coverage matches the reported config: The PR adds a maybeRepairCodexRoutes test with model.primary = openai-codex/gpt-5.4, a legacy model-map agentRuntime: { id: "pi" }, Codex auth order, and disabled Codex plugin, then asserts the migrated canonical entry preserves { id: "pi" } and removes the legacy key. (src/commands/doctor/shared/codex-route-warnings.test.ts:3368, 6952ea7b650a)
  • Existing tests define adjacent intended behavior: Current tests already preserve explicit model-scoped runtime pins for model-map-only repairs and overwrite non-concrete auto runtime pins when preserving Codex route intent, which supports the PR's narrow treatment of concrete pins. (src/commands/doctor/shared/codex-route-warnings.test.ts:2974, 5c9a8f33b312)
  • PR proof shows before/after real behavior: The PR body includes copied terminal output from invoking the production maybeRepairCodexRoutes function against the reported config on baseline and patched worktrees, showing the previous { id: "codex" } result and the fixed { id: "pi" } result. (6952ea7b650a)
  • Exact-head checks were not finished: Live check-run data for head 6952ea7b650a9343f67bda6306fc619002f49706 showed several GitHub Actions jobs queued or pending at review time, so merge should remain gated on required checks. (6952ea7b650a)

Likely related people:

  • pashpashpash: Recent OpenAI Codex routing and runtime-policy commits on codex-route-warnings.ts introduced and carried the provider/model-scoped migration surface this PR adjusts. (role: introduced behavior / feature owner; confidence: high; commits: 1c3399010815, 02fe0d8978db, 52771b65e27c; files: src/commands/doctor/shared/codex-route-warnings.ts)
  • joshavant: Recent doctor Codex plugin/runtime repair work touched the same file, and the linked canonical issue is currently assigned to this handle in live GitHub data. (role: recent area contributor; confidence: medium; commits: c5b33523266c; files: src/commands/doctor/shared/codex-route-warnings.ts, src/commands/doctor/shared/codex-route-warnings.test.ts)
  • steipete: Recent config and doctor migration commits touched the same central doctor route-warning file and adjacent model schema behavior. (role: recent area contributor; confidence: medium; commits: e96428b008a9, 031655b93343; files: src/commands/doctor/shared/codex-route-warnings.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 5c9a8f33b312.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 19, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper clawsweeper Bot added the clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper saw the passing review, but the PR needs another repair pass before merge.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=6952ea7b650a9343f67bda6306fc619002f49706); CHANGELOG.md entry is required before automerge; dispatch a focused changelog repair
Action: repair worker queued. Run: https://github.com/openclaw/clawsweeper/actions/runs/26131302864
Model: gpt-5.5

I will update this PR branch, or open a safe credited replacement, if the repair worker finds a narrow CI fix.

Automerge progress:

  • 2026-05-19 20:57:33 UTC review queued 47bd8c1f7f7c (after repair)
  • 2026-05-19 21:02:55 UTC review passed 47bd8c1f7f7c (structured ClawSweeper verdict: pass (sha=47bd8c1f7f7c7d273c944041e8373fbcac658...)
  • 2026-05-19 21:21:00 UTC review queued 47bd8c1f7f7c (queued)
  • 2026-05-19 22:22:36 UTC review passed 6952ea7b650a (structured ClawSweeper verdict: pass (sha=6952ea7b650a9343f67bda6306fc619002f49...)

@clawsweeper clawsweeper Bot force-pushed the fix/84038-preserve-pi-runtime branch from 9d93263 to 47bd8c1 Compare May 19, 2026 20:57
@clawsweeper clawsweeper Bot added status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels May 19, 2026
nxmxbbd and others added 2 commits May 20, 2026 06:16
Adds a regression test in codex-route-warnings.test.ts that mirrors
the exact user-reported config from openclaw#84038:

- agents.defaults.model.primary = "openai-codex/gpt-5.4"
- agents.defaults.models["openai-codex/gpt-5.4"].agentRuntime = { id: "pi" }

The user expects doctor --fix to preserve the explicit non-default
runtime pin while migrating the legacy model ref. Current behavior:
the pin is silently overwritten by a synthesized { id: "codex" }
during the rewriteModelsMap spread-merge.

This commit intentionally lands the test RED so a follow-up fix can
flip it to GREEN with a focused diff.

Co-Authored-By: Nex <nex@dbitstec.com>
…gacy model-ref migration

`maybeRepairCodexRoutes` migrates `openai-codex/*` model refs to their
canonical `openai/*` form, then calls `ensureCodexRuntimePolicy` on each
hit so the rewritten ref keeps Codex auth routing. When the user already
had an explicit non-default pin on the legacy form (for example
`models["openai-codex/gpt-5.4"].agentRuntime = { id: "pi" }` as a
deliberate opt-out from the broken native Codex runtime), the previous
sequence silently overwrote it:

1. The `AGENT_MODEL_CONFIG_KEYS` loop rewrote `model.primary`, then
   `preserveCodexRuntimePolicyForNewHits` synthesized
   `models["openai/gpt-5.4"].agentRuntime = { id: "codex" }` because
   that canonical entry did not yet exist.
2. `rewriteModelsMap` then renamed `openai-codex/gpt-5.4` →
   `openai/gpt-5.4`. Its spread merge
   `{ ...legacyRecord, ...canonicalRecord }` placed the synthesized
   `{ id: "codex" }` last, silently dropping the user's `{ id: "pi" }`
   pin.

End state: the explicit PI runtime opt-out was gone, gateway fell back
to the native Codex runtime, and per-request token usage grew 3-4x
because the upstream Codex runtime issue still applies.

Fix: in `ensureCodexRuntimePolicy`, skip synthesizing the default
`{ id: "codex" }` pin when the user already has an explicit non-default
pin on a legacy `openai-codex/*` form of the same canonical model ref.
`rewriteModelsMap` then carries the legacy pin forward unmodified via
its existing `canonicalEntry ?? legacyEntry` falsy branch.

This narrow fix matches the existing behavior covered by
`preserves explicit model-scoped runtime pins when repairing legacy
model map keys` (only the models map, no `model.primary` slot to trigger
synthesis) and `overwrites non-concrete model-scoped runtime pins when
preserving Codex route intent` (auto/default pins still get codex
applied). The combined-config path from openclaw#84038 was the previously
uncovered regression.

Fixes openclaw#84038.

Co-Authored-By: Nex <nex@dbitstec.com>
@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the contribution. The source branch was not safely writable by ClawSweeper, so it opened a replacement PR and kept the credit trail visible.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #84362
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this one because the run was configured to close superseded source PRs after opening the replacement.
The replacement PR keeps the contributor trail visible for review and changelog credit.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 41e5043.

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

Labels

clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge commands Command implementations P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: doctor --fix silently migrates intentional openai-codex/ config to openai/, breaking PI+OAuth runtime and causing 3-4x token inflation

2 participants