Skip to content

fix(providers): drop a removed provider's custom models (#1273) - #1293

Merged
lidge-jun merged 1 commit into
devfrom
codex/260808-1273-provider-remove-custom-models
Aug 8, 2026
Merged

fix(providers): drop a removed provider's custom models (#1273)#1293
lidge-jun merged 1 commit into
devfrom
codex/260808-1273-provider-remove-custom-models

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Partial fix for #1273 — the first of the two defects in that report. It does not close the issue; see "What this does not fix" below.

Removing a provider deleted only config.providers[name]. Its rows in config.customModels stayed behind, and neither consumer filters them: /api/models lists every row (src/server/management/model-rows.ts) and the generated Codex catalog emits every row keyed by slug (src/codex/catalog/provider-fetch.ts). The dashboard kept advertising models that resolved to a provider which no longer existed.

This is an inconsistency rather than a missing feature. Provider rename already maintains the array — rewriteProviderReferences rewrites customModels[].provider alongside combo targets and Claude tier maps — so the array is meant to track the provider lifecycle, and one of the two sibling operations simply did not. dropProviderCustomModels is placed directly beside the rename pass so the two stay visible to each other.

Both removal paths call it and report the count: ocx provider remove prints it and adds droppedCustomModels to --json; the management DELETE /api/providers returns the same field.

Two deliberate details:

  • An emptied list drops the customModels field rather than leaving [], matching the add/remove routes.
  • The customModelCatalogMigration marker is preserved unchanged. It records one-time ownership of pre-marker rows, and rewriting it would change an older binary's view of what it may delete — the migration module warns against exactly that. Both integration tests assert the marker survives in the persisted config.json.

What this does not fix

The second defect in #1273 remains open: a stale in-memory config re-persisting deleted rows through a whole-document write. saveConfigPreservingClaudeCode reconciles claudeCode and the live server binding against its pre-write disk read, but not customModels, so a process holding a pre-deletion config resurrects the rows.

That needs a reconciliation design of its own, keyed on the immutable OcxCustomModel.id rather than on routedSlug (which is an encoding — both provider and modelId are mutable, so a renamed row reads as a delete plus an insert). It also has to cover providers itself, since the same stale write resurrects the provider record. Diagnosis and the writer-path inventory are recorded on the issue.

Verification

  • bun run test10009 pass / 7 skip / 0 fail across 626 files
  • bun test tests/provider-id-rewrite.test.ts tests/management-provider-validation.test.ts — 62 pass / 0 fail
  • bun test tests/cli-provider.test.ts — 30 pass / 0 fail
  • bun run typecheck — clean
  • bun run privacy:scan — passed

Each path proved independently, since a combined ablation would only have proved one of them:

  • reverting only src/server/management/provider-routes.ts → 61 pass / 1 fail, failing the new management API test
  • reverting only src/cli/provider.ts → 29 pass / 1 fail, failing the new CLI test, which spawns the real ocx provider remove and reads the persisted config

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. (No documented behavior changes; the new droppedCustomModels field is additive and reported only when non-zero.)
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. (No credential, auth, or workflow surface is touched; the change filters one config array.)

Reported by @gdxnpy, whose reproduction included a before/after config diff showing the cooperating save path itself was healthy — that detail is what made the second defect findable rather than "settings sometimes revert".

Summary by CodeRabbit

  • Bug Fixes

    • Removing a provider now also removes its associated custom models.
    • Custom models belonging to other providers remain unchanged.
    • Migration metadata is preserved during provider removal.
  • CLI & API

    • Provider deletion results report the number of removed custom models.
    • JSON and human-readable output use appropriate count formatting.

Removing a provider deleted only `config.providers[name]`. Its rows in
`config.customModels` stayed behind, and neither consumer filters them:
`/api/models` lists every row and the generated Codex catalog emits every
row keyed by slug, so the dashboard kept advertising models that resolved
to a provider which no longer existed.

This is an inconsistency rather than a missing feature. Provider *rename*
already maintains the array — `rewriteProviderReferences` rewrites
`customModels[].provider` alongside combo targets and Claude tier maps — so
the array is meant to track the provider lifecycle, and one of the two
sibling operations simply did not. `dropProviderCustomModels` is therefore
placed next to the rename pass, so the two stay visible to each other.

Both removal paths call it and report the count: `ocx provider remove`
prints it and adds `droppedCustomModels` to `--json`, and the management
DELETE returns the same field. An emptied list drops the `customModels`
field, matching the add/remove routes; the `customModelCatalogMigration`
marker is deliberately preserved, since rewriting it would change an older
binary's view of one-time row ownership.

Partial fix for #1273. The second defect in that report — a stale in-memory
config re-persisting deleted rows through a whole-document write — is not
addressed here and remains open; it needs a reconciliation design of its
own, keyed on the immutable `OcxCustomModel.id` rather than on a slug.

Reported by @gdxnpy with a reproduction and a before/after config diff that
showed the cooperating save path itself was healthy, which is what made the
second defect findable rather than "settings sometimes revert".
@github-actions github-actions Bot added the bug Something isn't working label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Provider deletion now removes custom models owned by the deleted provider. CLI and management API responses report the number removed. Migration metadata and models owned by other providers remain unchanged.

Changes

Provider Custom Model Cleanup

Layer / File(s) Summary
Custom-model removal helper
src/providers/provider-id-rewrite.ts:151-179, tests/provider-id-rewrite.test.ts:4, tests/provider-id-rewrite.test.ts:134-211
Adds dropProviderCustomModels, which removes matching models, deletes an empty customModels field, preserves migration metadata, and returns the removal count. Tests cover selective removal, no-op behavior, absent data, array identity, and metadata preservation.
CLI provider deletion integration
src/cli/provider.ts:15, src/cli/provider.ts:306, src/cli/provider.ts:317-326, tests/cli-provider.test.ts:226-263
CLI provider removal invokes the cleanup helper before saving. JSON and human-readable output report removed model counts. End-to-end coverage verifies unrelated models and migration metadata remain.
Management API provider deletion integration
src/server/management/provider-routes.ts:618-631, tests/management-provider-validation.test.ts:1075-1137
Management API provider deletion invokes cleanup before saving and returns droppedCustomModels when applicable. Tests verify API responses, persisted configuration, unrelated models, and migration metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant ProviderCLI
  participant ProviderRoutes
  participant Cleanup as dropProviderCustomModels
  participant Configuration

  Operator->>ProviderCLI: Remove provider
  ProviderCLI->>Cleanup: Remove provider-owned custom models
  Cleanup->>Configuration: Update customModels
  Cleanup-->>ProviderCLI: Return removed count
  ProviderCLI->>Configuration: Save configuration
  ProviderCLI-->>Operator: Report removal count

  ProviderRoutes->>Cleanup: Remove provider-owned custom models
  Cleanup-->>ProviderRoutes: Return removed count
  ProviderRoutes->>Configuration: Save configuration
  ProviderRoutes-->>Operator: Return droppedCustomModels
Loading

Possibly related PRs

Suggested labels: review-ready

Suggested reviewers: wibias, if2007, ingwannu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: removing custom models when a provider is removed.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/260808-1273-provider-remove-custom-models

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/provider.ts`:
- Around line 317-318: Always include droppedCustomModels in successful deletion
results: update src/cli/provider.ts lines 317-318 to emit 0 when no custom
models are removed, and update src/server/management/provider-routes.ts lines
626-630 to do the same for every successful response. Add zero-removal
assertions to both integration tests, while preserving the existing
positive-count reporting.

In `@src/server/management/provider-routes.ts`:
- Around line 618-621: The provider-removal flow must load
dropProviderCustomModels before mutating config so no await occurs between
provider deletion and cleanup. Update the surrounding provider deletion handler
to use a static import or pre-load the helper with
saveConfigPreservingClaudeCode, then perform provider deletion,
dropProviderCustomModels, setProviderContextCap, and save in one synchronous
mutation section.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8aed220f-3c9b-404c-bf26-d5a04af41670

📥 Commits

Reviewing files that changed from the base of the PR and between 57ea8df and 472015e.

📒 Files selected for processing (6)
  • src/cli/provider.ts
  • src/providers/provider-id-rewrite.ts
  • src/server/management/provider-routes.ts
  • tests/cli-provider.test.ts
  • tests/management-provider-validation.test.ts
  • tests/provider-id-rewrite.test.ts

Comment thread src/cli/provider.ts
Comment on lines +317 to 318
...(droppedCustomModels > 0 ? { droppedCustomModels } : {}),
}, null, 2));

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Always return droppedCustomModels.

When a provider has no custom models, both responses omit droppedCustomModels. Clients cannot distinguish a zero cleanup count from an older server that does not implement this lifecycle cleanup.

  • src/cli/provider.ts#L317-L318: Emit droppedCustomModels: 0 when no rows are removed.
  • src/server/management/provider-routes.ts#L626-L630: Emit droppedCustomModels: 0 in every successful deletion response.

Add zero-removal assertions to both integration tests.

As per PR objectives, “Report the number removed via CLI output, --json.droppedCustomModels, and the API response”.

📍 Affects 2 files
  • src/cli/provider.ts#L317-L318 (this comment)
  • src/server/management/provider-routes.ts#L626-L630
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/provider.ts` around lines 317 - 318, Always include
droppedCustomModels in successful deletion results: update src/cli/provider.ts
lines 317-318 to emit 0 when no custom models are removed, and update
src/server/management/provider-routes.ts lines 626-630 to do the same for every
successful response. Add zero-removal assertions to both integration tests,
while preserving the existing positive-count reporting.

Comment on lines +618 to 621
const { dropProviderCustomModels } = await import("../../providers/provider-id-rewrite");
const droppedCustomModels = dropProviderCustomModels(config, name);
setProviderContextCap(config, name, false);
save(config);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Import the cleanup helper before mutating config.

Line 617 removes the provider from the live configuration. Line 618 then yields on a dynamic import before it removes the provider-owned custom models. A concurrently resuming request can observe a deleted provider with its orphaned custom models still present.

Load dropProviderCustomModels with saveConfigPreservingClaudeCode before line 616, or use a static import. Keep provider deletion, custom-model cleanup, context-cap cleanup, and saving in one synchronous mutation section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/provider-routes.ts` around lines 618 - 621, The
provider-removal flow must load dropProviderCustomModels before mutating config
so no await occurs between provider deletion and cleanup. Update the surrounding
provider deletion handler to use a static import or pre-load the helper with
saveConfigPreservingClaudeCode, then perform provider deletion,
dropProviderCustomModels, setProviderContextCap, and save in one synchronous
mutation section.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant