fix: Config.update() writes to wrong file, PATCH /config settings silently lost#505
fix: Config.update() writes to wrong file, PATCH /config settings silently lost#505saravmajestic wants to merge 3 commits intomainfrom
Conversation
…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.
There was a problem hiding this comment.
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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/opencode/src/config/config.ts (1)
1431-1434: Preserve.jsonccontent when updating project config.If
filepathresolves toopencode.jsonc, Line 1434 rewrites withwriteJson, which strips comments/JSONC formatting. Consider patching JSONC in-place (same approach asupdateGlobal) 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
📒 Files selected for processing (1)
packages/opencode/src/config/config.ts
`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>
Summary
Config.update()wrote toconfig.jsonin the project directory, butConfig.state()loads project config fromopencode.json/opencode.jsoncviaConfigPaths.projectFiles("opencode", ...). The file names didn't match, so any setting saved throughPATCH /configwas written to a file that was never read back.No endpoints found for google/gemini-3-pro-previewwhen the default model was stale/removed from OpenRouter.ConfigPaths.projectFiles()thatstate()uses, falling back toopencode.jsonif none exists.Root Cause
state()?Config.update()(before fix)config.jsonGlobal.Path.config, never from project dirConfig.state()project loadingopencode.json/opencode.jsoncConfig.update()(after fix)ConfigPaths.projectFiles()or falls back toopencode.jsonHow it was found
APIError:with no message when sending messagesNo endpoints found for google/gemini-3-pro-preview(a stale default model)PATCH /configwith{"model": "openrouter/anthropic/claude-sonnet-4.6"}→ returned successGET /configright after didn't include themodelfield — it was lostConfig.update()writingconfig.jsonwhileConfig.state()readsopencode.jsonTest plan
PATCH /configwith{"model": "openrouter/anthropic/claude-sonnet-4.6"}GET /config— verifymodelfield is present in responseopencode.json/opencode.jsoncfiles are updated in-place (not a new file created)opencode.json🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests
Chores