Skip to content

fix(providers): keep hand-edited context windows through a POST overwrite - #1535

Merged
lidge-jun merged 2 commits into
devfrom
codex/1409-preserve-context-window-overrides
Aug 12, 2026
Merged

fix(providers): keep hand-edited context windows through a POST overwrite#1535
lidge-jun merged 2 commits into
devfrom
codex/1409-preserve-context-window-overrides

Conversation

@lidge-jun

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

Copy link
Copy Markdown
Owner

Summary

POST /api/providers loses hand-edited context windows when it overwrites an existing provider.

ProviderPayload (gui/src/provider-payload.ts) has no member for contextWindow or modelContextWindows, so the dashboard's add/edit form structurally cannot send them. On an overwrite those fields are therefore absent, enrichProviderFromCatalogenrichProviderFromRegistry fills them from the registry seed, and the stored row becomes the seed. For opencode-go the seed is exactly {"kimi-k3": 262144} — which is what #1409 reports finding in place of a hand-edited {"deepseek-v4-flash": 900000}.

The fix follows the precedent already in this function: apiKeyPool and modelCosts are carried across the same path, with the same rationale — the form does not send them, so absence must not mean deletion. These two fields are the same class of user-owned data and were simply never added to the list.

Two details that matter for review:

  • Ownership is sampled before enrichment. After enrichProviderFromCatalog runs, "the client omitted this field" and "the registry supplied it" are indistinguishable, so a carry-over guard written as prov.x === undefined afterwards can never fire. An earlier draft had exactly that bug and its test would have stayed red.
  • When the client omits the map, the stored value is the user's map alone. Merging the registry seed in would persist seed keys into user config as a side effect of an unrelated save, and router.ts already fills registry values beneath user entries at resolve time via mergeRecordFill.

Deletion is unaffected: it goes through PATCH with an explicit null, which the carry-over must not undo, and that is covered by a test.

Why this does not carry Closes #1409

The confirmed reproduction is a duplicate-name POST through the Add Provider modal. The dashboard's ordinary editing surfaces use PATCH (gui/src/pages/use-providers-crud.ts), and Models.tsx sends modelContextWindows over PATCH, which merges per key and already preserves unmentioned entries.

The reporter's sequence was an upgrade, a daemon restart, and a later unrelated full-config write. That points at the stale whole-document writer tracked in #1273, and nothing here rules it in or out. So this PR fixes a real, demonstrated data-loss defect on its own merits, and #1409 gets a comment describing what was confirmed rather than a closure it has not earned.

Verification

On a Linux runner (Bun 1.3.14):

  • bun x tsc --noEmit — exit 0
  • bun test tests/management-provider-validation.test.ts — 60 pass, 0 fail
  • 60-file provider/config/management sweep — 1023 pass, 0 fail

Red-before evidence: with only the test diff applied to unmodified dev, 3 of the new tests fail.

New coverage: an omitted map keeps the user's map and does not gain registry seed keys; a submitted map updates that key while other user keys survive; an omitted scalar contextWindow is preserved; a submitted scalar still wins; a brand-new provider still receives the registry seed (no regression in enrichment); and PATCH can still delete a key with an explicit null.

This touches a management write boundary, so it wants the security review src/AGENTS.md asks for — no auth, credential, or transport behavior changes, but the persisted-config write path does.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Refs #1409, #1273

Summary by CodeRabbit

  • Bug Fixes

    • Preserved existing provider context-window settings when updates omit those fields.
    • Correctly merged submitted per-model context-window values during provider updates.
    • Prevented registry defaults from being saved as user-configured settings.
    • New providers still receive appropriate registry defaults.
    • Explicitly clearing model-specific settings continues to work as expected.
  • Tests

    • Added regression coverage for provider creation, updates, merging, preservation, and clearing of context-window settings.

…rite

The dashboard's provider payload type has no member for contextWindow or
modelContextWindows, so an overwrite arrives without them. Registry
enrichment then fills the absent fields from the seed and the stored row
loses the user's values. For opencode-go the seed is exactly
{"kimi-k3": 262144}, which is what #1409 reports finding in place of a
hand-edited deepseek-v4-flash entry.

apiKeyPool and modelCosts are already carried across this path for the
same reason: the form does not send them, so absence must not mean
deletion. These two fields are the same class of user data and were
simply never added.

Ownership is sampled before enrichment. After enrichProviderFromCatalog
runs, an absent field and a registry-seeded one are indistinguishable, so
a guard written as prov.x === undefined afterwards can never fire.

When the client omits the map the stored value is the user's map alone.
Merging the registry seed in would persist seed keys into user config as
a side effect of an unrelated save, and router.ts already fills registry
values beneath user entries at resolve time.

This does not close #1409. The confirmed reproduction is a duplicate-name
POST through Add Provider; the reporter's sequence was an upgrade plus a
later full-config write, which points at the stale whole-document writer
in #1273 and is not established here.

Refs #1409, #1273
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The provider POST route now preserves user-supplied context-window settings during overwrites, merges model-specific values, and excludes registry-seeded keys when fields are omitted. Regression tests cover POST and PATCH behavior. The verification command now targets the management-provider validation suite.

Changes

Provider context preservation

Layer / File(s) Summary
Preserve explicit context-window settings
src/server/management/provider-routes.ts
The POST route records whether contextWindow and modelContextWindows were supplied before catalog enrichment. Provider overwrites preserve omitted scalar values and merge submitted model-specific values without persisting registry-seeded keys.
Validate overwrite behavior
tests/management-provider-validation.test.ts, devlog/_plan/260812_five_bug_fix_campaign/040_phase4_issue1409_context_window_overrides.md
End-to-end tests cover preservation, merging, registry defaults for new providers, and explicit PATCH deletion with null. The verification command targets the existing management-provider validation suite and documents the corrected test path.

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

Possibly related PRs

  • lidge-jun/opencodex#1203: Both changes modify provider context-window overwrite handling and its validation tests.
  • lidge-jun/opencodex#1197: Both changes address provider catalog enrichment and persistence of registry-derived defaults.
  • lidge-jun/opencodex#615: Both changes modify provider configuration handling involving registry-derived provider metadata.

Suggested reviewers: wibias, ingwannu

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR fixes POST overwrite loss, but #1409 also requires survival across upgrades, restarts, and unrelated full-config writes, which remain unresolved. Update the stale whole-document writer identified in #1273 and add an end-to-end regression test covering upgrades, restarts, and unrelated provider writes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: preserving hand-edited provider context-window values during POST overwrites.
Out of Scope Changes check ✅ Passed The route change, regression tests, and verification-note update directly support provider context-window preservation and the linked issue.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/1409-preserve-context-window-overrides

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

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

Copy link
Copy Markdown

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: 9ae7e21b27

ℹ️ 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 on lines +383 to +385
prov.modelContextWindows = submittedModelContextWindows
? { ...existing.modelContextWindows, ...(prov.modelContextWindows ?? {}) }
: { ...existing.modelContextWindows };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve registry refreshes when carrying context windows

When a registry provider was originally created, enrichment persisted that release's modelContextWindows seed into the provider row even if the user never edited it. If a later release corrects the canonical seed and the dashboard performs a duplicate-name POST (which omits this field), this assignment replaces the freshly enriched map with the old persisted seed; routedProviderConfig then gives that provider map precedence, so the registry correction never takes effect. Preserve only identifiable user overrides—such as by tracking provenance or normalizing stored seed values—instead of treating the entire existing map as user-owned.

AGENTS.md reference: src/AGENTS.md:L18-L18

Useful? React with 👍 / 👎.

tests/provider-routes*.test.ts does not exist, and an unmatched glob
aborts the run under zsh. Point at the suite that actually holds this
path's coverage.

@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 `@tests/management-provider-validation.test.ts`:
- Around line 523-537: Extend the test “PATCH can still delete a key with an
explicit null” to use the registry-seeded “kimi-k3” key, delete it via PATCH,
then call seedProvider(server.url, {}) and assert it remains absent from
persisted modelContextWindows. Replace the current “deepseek-v4-flash” assertion
so the test detects POST carry-over re-persisting the registry seed.
- Around line 510-517: Update the assertion in the “a brand-new provider still
receives the registry seed” test to verify that
providers["opencode-go"].modelContextWindows exactly matches the documented
{"kimi-k3": 262144} mapping, rather than only checking that the map is defined.
🪄 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: 65ed53c4-5640-4632-9db5-3713e52f20e0

📥 Commits

Reviewing files that changed from the base of the PR and between 9e777fb and 4a11d27.

📒 Files selected for processing (3)
  • devlog/_plan/260812_five_bug_fix_campaign/040_phase4_issue1409_context_window_overrides.md
  • src/server/management/provider-routes.ts
  • tests/management-provider-validation.test.ts

Comment on lines +510 to +517
test("a brand-new provider still receives the registry seed", async () => {
freshHome();
const server = startServer(0);
try {
expect((await seedProvider(server.url, {})).status).toBe(200);

// No prior row exists, so enrichment is authoritative and the seed must land.
expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toBeDefined();

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the expected registry seed.

Line 517 accepts any defined map. The test passes if registry enrichment writes the wrong model ID or window. Assert the documented {"kimi-k3": 262144} value.

Proposed fix
-        expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toBeDefined();
+        expect(loadConfig().providers["opencode-go"]?.modelContextWindows)
+          .toEqual({ "kimi-k3": 262144 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("a brand-new provider still receives the registry seed", async () => {
freshHome();
const server = startServer(0);
try {
expect((await seedProvider(server.url, {})).status).toBe(200);
// No prior row exists, so enrichment is authoritative and the seed must land.
expect(loadConfig().providers["opencode-go"]?.modelContextWindows).toBeDefined();
test("a brand-new provider still receives the registry seed", async () => {
freshHome();
const server = startServer(0);
try {
expect((await seedProvider(server.url, {})).status).toBe(200);
// No prior row exists, so enrichment is authoritative and the seed must land.
expect(loadConfig().providers["opencode-go"]?.modelContextWindows)
.toEqual({ "kimi-k3": 262144 });
🤖 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 `@tests/management-provider-validation.test.ts` around lines 510 - 517, Update
the assertion in the “a brand-new provider still receives the registry seed”
test to verify that providers["opencode-go"].modelContextWindows exactly matches
the documented {"kimi-k3": 262144} mapping, rather than only checking that the
map is defined.

Comment on lines +523 to +537
test("PATCH can still delete a key with an explicit null", async () => {
freshHome();
const server = startServer(0);
try {
expect((await seedProvider(server.url, { modelContextWindows: { "deepseek-v4-flash": 900000 } })).status).toBe(200);

const patch = await fetch(new URL("/api/providers?name=opencode-go", server.url), {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ modelContextWindows: { "deepseek-v4-flash": null } }),
});
expect(patch.status).toBe(200);

// Deletion is an explicit null through PATCH, which the POST carry-over must not undo.
expect(loadConfig().providers["opencode-go"]?.modelContextWindows?.["deepseek-v4-flash"]).toBeUndefined();

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 | 🔵 Trivial | ⚡ Quick win

Test deletion across a later POST overwrite.

The test comment states that POST carry-over must not undo deletion, but the test only checks the immediate PATCH result. Use a registry-seeded key such as "kimi-k3", delete it with PATCH, then call seedProvider(server.url, {}) and assert that "kimi-k3" remains absent from persisted modelContextWindows. The current "deepseek-v4-flash" case cannot detect accidental re-persistence of the opencode-go registry seed.

🤖 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 `@tests/management-provider-validation.test.ts` around lines 523 - 537, Extend
the test “PATCH can still delete a key with an explicit null” to use the
registry-seeded “kimi-k3” key, delete it via PATCH, then call
seedProvider(server.url, {}) and assert it remains absent from persisted
modelContextWindows. Replace the current “deepseek-v4-flash” assertion so the
test detects POST carry-over re-persisting the registry seed.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Full-suite result on the Linux runner at this branch head:

11316 pass
0 fail
EXIT=0

All four Linux CI shards are green here as well.

@lidge-jun
lidge-jun merged commit b310d18 into dev Aug 12, 2026
27 checks passed
@lidge-jun
lidge-jun deleted the codex/1409-preserve-context-window-overrides branch August 12, 2026 12:58
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