Skip to content

fix: Config.update() writes to wrong file, PATCH /config settings silently lost#505

Open
saravmajestic wants to merge 3 commits intomainfrom
fix/config-update-wrong-filename
Open

fix: Config.update() writes to wrong file, PATCH /config settings silently lost#505
saravmajestic wants to merge 3 commits intomainfrom
fix/config-update-wrong-filename

Conversation

@saravmajestic
Copy link
Copy Markdown

@saravmajestic saravmajestic commented Mar 27, 2026

Summary

  • Config.update() wrote to config.json in the project directory, but Config.state() loads project config from opencode.json / opencode.jsonc via ConfigPaths.projectFiles("opencode", ...). The file names didn't match, so any setting saved through PATCH /config was written to a file that was never read back.
  • This caused the VS Code extension's model picker to appear to save the selection, but the server would fall back to the provider-level default model on the next request — leading to errors like No endpoints found for google/gemini-3-pro-preview when the default model was stale/removed from OpenRouter.
  • The fix finds the existing project config file using the same ConfigPaths.projectFiles() that state() uses, falling back to opencode.json if none exists.

Root Cause

Function File it uses Loaded by state()?
Config.update() (before fix) config.json No — only loaded from Global.Path.config, never from project dir
Config.state() project loading opencode.json / opencode.jsonc Yes
Config.update() (after fix) Uses ConfigPaths.projectFiles() or falls back to opencode.json Yes

How it was found

  1. VS Code extension's Altimate Code Chat showed APIError: with no message when sending messages
  2. The error was actually No endpoints found for google/gemini-3-pro-preview (a stale default model)
  3. User selected Claude Sonnet 4.6 in the model picker UI → extension called PATCH /config with {"model": "openrouter/anthropic/claude-sonnet-4.6"} → returned success
  4. But GET /config right after didn't include the model field — it was lost
  5. Traced to Config.update() writing config.json while Config.state() reads opencode.json

Test plan

  • Call PATCH /config with {"model": "openrouter/anthropic/claude-sonnet-4.6"}
  • Call GET /config — verify model field is present in response
  • Send a message — verify it uses the configured model, not the provider default
  • Verify existing opencode.json / opencode.jsonc files are updated in-place (not a new file created)
  • Verify fresh project with no config file creates opencode.json

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Configuration updates now save to the correct project config file (falls back to opencode.json), so persisted settings are found by subsequent operations.
  • Tests

    • Updated tests to expect config persistence at the corrected filename.
  • Chores

    • Cleaned up a TypeScript annotation related to a database driver import.

…fig` are silently lost

`Config.update()` wrote to `config.json` in the project directory, but
`Config.state()` loads project config from `opencode.json` / `opencode.jsonc`
via `ConfigPaths.projectFiles("opencode", ...)`. The file names didn't match,
so any setting saved through `PATCH /config` (model selection, provider config,
etc.) was written to a file that was never read back.

This caused the VS Code extension's model picker to appear to save the
selection but the server would fall back to the provider-level default model
on the next request — leading to errors like "No endpoints found for
google/gemini-3-pro-preview" when the default model was stale.

The fix finds the existing project config file using the same
`ConfigPaths.projectFiles()` that `state()` uses, falling back to
`opencode.json` if none exists.
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fa4a786a-c11f-4209-b3de-62cafd36d597

📥 Commits

Reviewing files that changed from the base of the PR and between de3bd94 and bc852ac.

📒 Files selected for processing (2)
  • packages/drivers/src/sqlserver.ts
  • packages/opencode/test/config/config.test.ts
💤 Files with no reviewable changes (1)
  • packages/drivers/src/sqlserver.ts

📝 Walkthrough

Walkthrough

The Config.update() method now writes merged configuration JSON to the last existing project config file returned by ConfigPaths.projectFiles(...), falling back to Instance.directory/opencode.json when none exist. A TypeScript suppression comment was removed from a dynamic mssql import in the SQL Server driver, and a test expectation updated accordingly.

Changes

Cohort / File(s) Summary
Config write/update
packages/opencode/src/config/config.ts
Config.update() now writes to the last existing project config file from ConfigPaths.projectFiles("opencode", ...), with fallback to Instance.directory/opencode.json instead of always config.json.
Tests (expectation update)
packages/opencode/test/config/config.test.ts
Test updated to expect opencode.json as the persisted file path after Config.update(...) instead of config.json.
SQL Server driver cleanup
packages/drivers/src/sqlserver.ts
Removed an inline TypeScript suppression comment (// @ts-expect-error ...) that annotated the dynamic import("mssql") inside connect(); no runtime behavior changed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through files to fix the plight,
Writes now land where the readers write,
Tests adjusted, warnings shed,
A tidy trail where configs tread. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically identifies the bug being fixed (Config.update() writes to wrong file) and its user-facing impact (PATCH /config settings silently lost).
Description check ✅ Passed The description includes all required template sections: a comprehensive Summary explaining the root cause and fix, a detailed Test Plan with specific verification steps, and a Checklist with relevant items. All critical information is present and well-documented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/config-update-wrong-filename

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/opencode/src/config/config.ts (1)

1431-1434: Preserve .jsonc content when updating project config.

If filepath resolves to opencode.jsonc, Line 1434 rewrites with writeJson, which strips comments/JSONC formatting. Consider patching JSONC in-place (same approach as updateGlobal) to avoid destructive edits.

Suggested diff
   const projectFiles = await ConfigPaths.projectFiles("opencode", Instance.directory, Instance.worktree)
   const filepath = projectFiles[projectFiles.length - 1] ?? path.join(Instance.directory, "opencode.json")
-  const existing = await loadFile(filepath)
-  await Filesystem.writeJson(filepath, mergeDeep(existing, config))
+  if (filepath.endsWith(".jsonc")) {
+    const before = await Filesystem.readText(filepath).catch(() => "{}")
+    const updated = patchJsonc(before, config)
+    parseConfig(updated, filepath)
+    await Filesystem.write(filepath, updated)
+  } else {
+    const existing = await loadFile(filepath)
+    await Filesystem.writeJson(filepath, mergeDeep(existing, config))
+  }
   await Instance.dispose()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/config/config.ts` around lines 1431 - 1434, The current
write path always uses Filesystem.writeJson which will strip comments when
filepath is a .jsonc; change the update to preserve JSONC by detecting when
filepath endsWith(".jsonc") and applying the same in-place JSONC patching logic
used by updateGlobal instead of Filesystem.writeJson — use the existing
loadFile/mergeDeep result to compute the patch and call the JSONC patch/update
helper (the same routine updateGlobal uses) for .jsonc files, falling back to
Filesystem.writeJson for plain .json files; keep references to
ConfigPaths.projectFiles, loadFile, mergeDeep and Filesystem.writeJson when
locating the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/opencode/src/config/config.ts`:
- Around line 1431-1434: The current write path always uses Filesystem.writeJson
which will strip comments when filepath is a .jsonc; change the update to
preserve JSONC by detecting when filepath endsWith(".jsonc") and applying the
same in-place JSONC patching logic used by updateGlobal instead of
Filesystem.writeJson — use the existing loadFile/mergeDeep result to compute the
patch and call the JSONC patch/update helper (the same routine updateGlobal
uses) for .jsonc files, falling back to Filesystem.writeJson for plain .json
files; keep references to ConfigPaths.projectFiles, loadFile, mergeDeep and
Filesystem.writeJson when locating the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 973d44b2-5d00-4cc2-88cd-25c7a549bd23

📥 Commits

Reviewing files that changed from the base of the PR and between abcaa1d and de3bd94.

📒 Files selected for processing (1)
  • packages/opencode/src/config/config.ts

saravmajestic and others added 2 commits March 27, 2026 19:47
`Config.update()` now writes to `opencode.json` (via `ConfigPaths.projectFiles()`),
not the legacy `config.json`. Update the assertion to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… provides declarations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant