Skip to content

fix(models): honor replace mode in list output#103103

Draft
steipete wants to merge 2 commits into
mainfrom
codex/fix-models-mode-replace-list
Draft

fix(models): honor replace mode in list output#103103
steipete wants to merge 2 commits into
mainfrom
codex/fix-models-mode-replace-list

Conversation

@steipete

@steipete steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes #94705. Supersedes #94735 because its stale fork branch could not safely accept the current-main maintainer refactor.

With models.mode: "replace", the default openclaw models list output still appended models from authenticated providers that were absent from models.providers. That contradicted replace-mode configuration semantics and made the configured-model list look broader than the effective configuration.

Why This Change Was Made

The authenticated-catalog append helper now reads the canonical config directly and returns early in replace mode. This keeps the policy at the row source that introduces the leak, avoids threading a derived boolean through the row-building context, and leaves explicit --all and --provider browsing on their existing source-plan paths.

The regression coverage exercises both merge and replace semantics, proves replace mode skips the authenticated catalog entirely, and separately protects explicit all/provider browsing. The replacement commit preserves original contributor credit for @Monkey-wusky.

User Impact

openclaw models list now shows only explicitly configured provider models when models.mode is replace. Users can still browse the complete catalog with --all or narrow catalog browsing with --provider.

Evidence

  • Current-main source audit: the default configured-list path is the only caller of the authenticated-catalog append helper; explicit all/provider paths use the source planner and remain unaffected.
  • Sanitized AWS Crabbox baseline on original exact head bb6a70e0c257cd2213670478156a638cc1c5ac97: src/commands/models.list.e2e.test.ts passed 20/20 (run_59f7ce52973a).
  • Isolated-state live CLI proof on that exact head: replace-mode default returned only custom/only, while --all and --provider xiaomi retained three Xiaomi catalog rows (run_c2b46c7f05e9).
  • Replacement exact head c45ceb843c0c045ca594eae0af5c47affb5f9d04, Testbox-through-Crabbox tbx_01kx4c0dgee775a7n3cvyy5512: forward-compat tests passed 29/29 and model-list E2E passed 20/20.
  • Exact-tree pnpm check:changed passed on Testbox, including core/core-test typechecks, changed-file lint, and repository guards.
  • Isolated-state live CLI proof on the exact replacement head: an implicit-transport configured model remained the sole default row; --all retained it plus three Xiaomi rows; --provider xiaomi returned only those three Xiaomi rows.
  • Fresh full-branch autoreview: clean, no accepted or actionable findings (patch is correct, confidence 0.97).
  • Hosted exact-head CI: pending after the draft is marked ready.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 10, 2026, 3:25 AM ET / 07:25 UTC.

Summary
The PR centralizes configured provider/model identity for model-list rows and makes replace-mode default output use configured-provider candidates while preserving explicit --all and --provider catalog browsing.

PR surface: Source +20, Tests +296. Total +316 across 9 files.

Reproducibility: yes. The exact-head source deterministically exposes both failures with z.ai configurations, and the PR author independently documented the same uncovered states.

Review metrics: 1 noteworthy metric.

  • Identity contracts: 3 coupled. Raw configured-provider identity, canonical display identity, and auth lookup identity currently share one candidate representation, which creates the two blockers.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #94705
Summary: This PR is the current replacement candidate for the canonical replace-mode model-list bug, but its exact head still needs repair.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦐 gold shrimp
Patch quality: 🦐 gold shrimp
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Repair the raw/canonical provider identity split and self-prefixed ID normalization.
  • [P1] Add focused tests and redacted exact-head CLI output for both repaired configurations.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: Live CLI evidence covers ordinary replace, all, and provider paths, but not the exact-head alias-auth or self-prefixed-ID states; add redacted terminal output after repair, update the PR body, and ask a maintainer for @clawsweeper re-review if automatic review does not run.

Risk before merge

  • [P1] Existing alias-backed auth profiles can make configured rows appear unavailable after merge because auth lookup uses only the canonical display provider.
  • [P1] A self-prefixed configured model ID under an aliased provider can render a malformed, non-canonical key that users cannot reliably select or compare.
  • [P1] The supplied real CLI proof does not cover either failing exact-head configuration.

Maintainer options:

  1. Repair both identity contracts (recommended)
    Normalize against raw config identity, canonicalize rendered identity afterward, and preserve raw auth-provider candidates with focused tests and live proof.
  2. Narrow the refactor
    Retain the replace-mode row-source fix but remove broader identity unification if it is not required to solve the canonical issue.
  3. Keep the branch draft
    Pause merge review until a revised exact head resolves both defects and demonstrates the repaired configurations.

Next step before merge

  • [P1] Both blockers are narrow mechanical identity repairs with clear focused regression and live-proof paths on the existing branch.

Security
Cleared: The patch changes local model-list row construction and tests only; no concrete security or supply-chain regression was found.

Review findings

  • [P2] Preserve raw provider identity for auth availability — src/commands/models/list.configured.ts:38
  • [P2] Normalize self-prefixed IDs before provider aliasing — src/commands/models/list.configured.ts:38-40
Review details

Best possible solution:

Normalize configured model IDs using the raw provider first, then canonicalize only the rendered key while carrying raw and canonical provider candidates separately for auth availability checks.

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

Yes. The exact-head source deterministically exposes both failures with z.ai configurations, and the PR author independently documented the same uncovered states.

Is this the best way to solve the issue?

No. The replace-mode source boundary is the right approach, but the current candidate shape conflates configured, display, and auth identities that have different contracts.

Full review comments:

  • [P2] Preserve raw provider identity for auth availability — src/commands/models/list.configured.ts:38
    The candidate stores only the canonical provider, so provider-auth fallback checks zai even when valid config and auth are indexed under z.ai. Carry the raw/source provider as an auth candidate so the configured row does not appear unavailable.
    Confidence: 0.98
  • [P2] Normalize self-prefixed IDs before provider aliasing — src/commands/models/list.configured.ts:38-40
    Canonicalizing z.ai to zai before normalizing model ID z.ai/glm-4.7 prevents prefix collapse and yields zai/z.ai/glm-4.7. Normalize the raw provider/model pair first, then apply the display alias.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.97

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 51c1c08227db.

Label changes

Label justifications:

  • P2: This is a normal-priority model-list correctness fix with a bounded CLI and provider-routing blast radius.
  • merge-risk: 🚨 compatibility: Existing aliased provider configurations can produce changed or malformed visible model keys after merge.
  • merge-risk: 🚨 auth-provider: Canonical row providers can fail to match valid auth profiles indexed under the configured provider alias.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: Live CLI evidence covers ordinary replace, all, and provider paths, but not the exact-head alias-auth or self-prefixed-ID states; add redacted terminal output after repair, update the PR body, and ask a maintainer for @clawsweeper re-review if automatic review does not run.
Evidence reviewed

PR surface:

Source +20, Tests +296. Total +316 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 6 163 143 +20
Tests 3 377 81 +296
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 9 540 224 +316

Acceptance criteria:

  • [P1] pnpm test src/commands/models/list.configured.test.ts.
  • [P1] pnpm test src/commands/models/list.rows.test.ts.
  • [P1] pnpm test src/commands/models/list.list-command.forward-compat.test.ts.
  • [P1] pnpm test src/commands/models.list.e2e.test.ts.
  • [P1] pnpm check:changed -- src/commands/models/list.configured.ts src/commands/models/list.rows.ts src/commands/models/provider-aliases.ts.

What I checked:

  • Raw alias auth identity is lost: Configured candidates replace rawProvider with the canonical display provider and retain no raw auth candidate; downstream availability fallback therefore queries zai even when valid config and auth profiles are indexed under z.ai. (src/commands/models/list.configured.ts:38, e735d949fbd1)
  • Self-prefixed model ID is normalized against the wrong provider: The code canonicalizes z.ai to zai before normalizing model ID z.ai/glm-4.7; the raw prefix no longer matches, so key construction can produce zai/z.ai/glm-4.7 instead of zai/glm-4.7. (src/commands/models/list.configured.ts:40, e735d949fbd1)
  • Previous findings are repaired: The new candidate map normalizes configured IDs before de-duplication, and replace mode now bypasses default/configured entries before emitting configured-provider rows; the two findings from the prior ClawSweeper cycle should not be re-raised. (src/commands/models/list.row-sources.ts:181, e735d949fbd1)
  • Current main still needs the central fix: Current main appends configured/default rows, configured-provider rows, and authenticated catalog rows unconditionally in the cheap default list path, so replace mode can still expose authenticated unconfigured providers. (src/commands/models/list.row-sources.ts:181, 51c1c08227db)
  • Exact-head live proof has an uncovered gap: The PR body supplies live CLI output for ordinary replace, all, and provider browsing, while the author explicitly notes that alias-backed auth identity and self-prefixed configured IDs were not exercised and remain defective. (e735d949fbd1)
  • Feature history: Peter Steinberger has the largest commit concentration across the central model-list files, while recent current-main work also involved saju01 on the shared row path. (src/commands/models/list.rows.ts:1, 51c1c08227db)

Likely related people:

  • steipete: Peter Steinberger has the largest historical commit concentration across the central model-list files and authored the current two-commit replacement branch after adapting the earlier contribution to the current architecture. (role: feature owner and recent refactor author; confidence: high; commits: f7bb38f2a090, e735d949fbd1; files: src/commands/models/list.configured.ts, src/commands/models/list.rows.ts, src/commands/models/provider-aliases.ts)
  • saju01: Recent current-main history and blame show work on the shared model-list row path, making this contributor relevant to interactions with the latest row-building architecture. (role: recent adjacent contributor; confidence: medium; commits: f0534c5b71db; files: src/commands/models/list.rows.ts)
  • Monkey-wusky: The older replacement target introduced and proved the central replace-mode behavior, and this PR preserves that contributor's credit while superseding the stale branch. (role: original fix contributor; confidence: medium; commits: bb6a70e0c257; files: src/commands/models/list.list-command.ts, src/commands/models/list.rows.ts)
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.

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.
Review history (3 earlier review cycles)
  • reviewed 2026-07-09T20:48:43.440Z sha 7d01ae6 :: needs real behavior proof before merge. :: [P2] Remove the release-owned changelog entry
  • reviewed 2026-07-09T20:54:51.096Z sha 7d01ae6 :: needs real behavior proof before merge. :: [P2] Remove the release-owned changelog entry
  • reviewed 2026-07-09T23:13:35.669Z sha f7bb38f :: needs real behavior proof before merge. :: [P2] Normalize configured model ids before de-duping | [P2] Filter stale defaults before replace-mode rows

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jul 9, 2026
@steipete
steipete force-pushed the codex/fix-models-mode-replace-list branch from 7d01ae6 to c45ceb8 Compare July 9, 2026 21:18
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations size: S maintainer Maintainer-authored PR labels Jul 9, 2026
@steipete
steipete marked this pull request as ready for review July 9, 2026 21:30
@steipete
steipete force-pushed the codex/fix-models-mode-replace-list branch from c45ceb8 to 6483ffd Compare July 9, 2026 21:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6483ffd019

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/models/list.rows.ts Outdated
Comment on lines +365 to +366
params.context.cfg.models?.mode !== "replace" &&
!shouldListConfiguredProviderModel({ providerConfig, model: configuredModel })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Canonicalize replace-mode configured provider keys

When models.mode: "replace" is used with a manifest provider alias in models.providers (for example "z.ai") and the model relies on plugin transport inference with no api, this new branch lets the row through but the code below still builds the row key/model from the raw provider key. resolveConfiguredEntries canonicalizes the same default/allowlist ref to zai/..., so seenKeys will not dedupe it and the default list can render both zai/glm-4.7 and z.ai/glm-4.7 (or expose only the raw alias for non-default configured rows), regressing the canonical configured-key behavior covered by the z.ai list tests. Canonicalize the provider before computing key/model for these replace-mode rows.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the codex/fix-models-mode-replace-list branch from 6483ffd to 5a3fb92 Compare July 9, 2026 22:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a3fb92c36

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/models/list.rows.ts Outdated
continue;
}
const key = modelKey(provider, configuredModel.id);
const key = modelKey(canonicalProvider, configuredModel.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve configured metadata for canonicalized default rows

When models.providers is keyed by an alias or non-normalized provider id (for example z.ai or XIAOMI) and the default/allowlist entry resolves to the canonical provider, appendConfiguredRows runs first and can only build a synthetic fallback because findConfiguredProviderModel looks up cfg.models.providers[entry.ref.provider] exactly. Fresh evidence in this version is that the provider-row key is now canonicalized here, so seenKeys treats the real configured provider row as a duplicate and skips it, leaving the default list with the fallback name/input/context instead of the configured model metadata in replace mode.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the codex/fix-models-mode-replace-list branch from 5a3fb92 to 3ea2f68 Compare July 9, 2026 22:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ea2f68fd8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/models/list.rows.ts Outdated
continue;
}
const key = modelKey(provider, configuredModel.id);
const key = modelKey(canonicalProvider, configuredModel.id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize configured model ids before de-duping

When models.mode: "replace" is used with a configured provider model that relies on transport inference and has a model id normalized elsewhere (for example google/gemini-3-pro-preview, which parseModelRef/configured catalogs normalize to gemini-3.1-pro-preview), this new replace-mode path adds a row keyed with the raw configuredModel.id. appendConfiguredRows has already emitted the canonical default/allowlist key, so seenKeys will not de-dupe it and the list can show both the canonical fallback row and the raw configured row, or expose non-default rows under an unselectable stale key. Normalize the configured model id before building key/model here.

Useful? React with 👍 / 👎.

Replace the stale fork PR with the reviewed fix on current main.

Refs: #94735

Co-authored-by: 毛宇豪0668001457 <[email protected]>
@steipete
steipete force-pushed the codex/fix-models-mode-replace-list branch from 3ea2f68 to f7bb38f Compare July 9, 2026 22:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7bb38f2a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/models/list.rows.ts Outdated
seenKeys: Set<string>;
}): Promise<void> {
// Replace narrows default configured lists; explicit browse paths skip this helper.
if (params.context.cfg.models?.mode === "replace") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter defaults in replace-mode listings

When models.mode: "replace" is set but agents.defaults.model.primary is absent or still points at a provider not present in models.providers, this guard only skips the authenticated catalog after appendConfiguredRows has already emitted that default/fallback row. The default models list output can still show something like openai/gpt-5.4 alongside the explicit provider rows, even though replace-mode CLI listings are supposed to be limited to models.providers.*.models (docs/concepts/models.md:67); filter configured entries against the configured provider models before appending them in replace mode.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Holding this replacement at exact head e735d949fbd12695ea87c0d27779f17251916a51; it is not land-ready despite green proof.

Two final P2 defects remain:

  • src/commands/models/list.rows.ts:99-115 derives the auth provider from the canonical row key. Model-catalog aliases are not guaranteed to be auth aliases, so valid z.ai config/profile auth can be indexed under z.ai while the rendered row is zai/... and is incorrectly shown unavailable.
  • src/commands/models/list.configured.ts:37-43 replaces the raw provider alias before constructing the configured candidate key. A self-prefixed configured model such as provider z.ai plus id z.ai/glm-4.7 can therefore become malformed as zai/z.ai/glm-4.7 instead of canonical zai/glm-4.7.

Exact-head CI run 29057561329, focused Testbox tests, check:changed, and the isolated CLI matrix all passed, but none exercised alias-backed auth identity plus a self-prefixed configured-provider id. Green proof therefore does not cover these invariants.

The next design must keep canonical row identity separate from raw/source auth-provider candidates, and must collapse the raw provider/model key before applying provider-alias canonicalization. Add focused coverage for both states before this leaves draft.

@steipete
steipete marked this pull request as draft July 9, 2026 23:46
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands Command implementations maintainer Maintainer-authored PR merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

models.mode: "replace" does not filter openclaw models list output

1 participant