-
Notifications
You must be signed in to change notification settings - Fork 670
feat: Add Model Provider configuration sync to ~/.claude/settings.json and fix activation issues #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds per-user model provider support: DB table and CRUD, API routes and UI for provider management, settings-file synchronization utilities, and runtime application of active provider overrides (API key, base URL, default model) into the Claude SDK when a userId is present. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as CredentialsSettings
participant API as /api/settings
participant DB as modelProvidersDb
participant SDK as server/claude-sdk
participant FS as ~/.claude/settings.json
User->>UI: Create provider (name, baseUrl, apiKey, modelId)
UI->>API: POST /api/settings/model-providers
API->>DB: createProvider(userId, ...)
DB-->>API: provider record
API->>FS: updateClaudeSettingsForProvider(provider)
FS-->>API: settings updated
API-->>UI: 200 OK + provider list
User->>UI: Activate provider
UI->>API: PATCH /api/settings/model-providers/:id/activate
API->>DB: setActiveProvider(userId, providerId)
DB-->>API: activated
API->>FS: updateClaudeSettingsForProvider(activeProvider)
API-->>UI: updated list
UI->>API: Send Claude command (WebSocket/message) including userId
API->>SDK: claude-command(options with userId)
SDK->>DB: getActiveProvider(userId)
DB-->>SDK: activeProvider
SDK->>SDK: apply overrides (apiKey, baseUrl, model)
SDK->>Anthropic: invoke with final config
Anthropic-->>SDK: response
SDK-->>API: stream result
API-->>UI: stream to user
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/routes/git.js (1)
595-666: Commit message generation ignores provider-specific default modelThe
/generate-commit-messageroute andgenerateCommitMessageWithAIcorrectly propagateuserIdintoqueryClaudeSDK, so the active provider’s API key/base URL are honored. However, in the Claude branch you always passmodel: "sonnet":await queryClaudeSDK(prompt, { cwd: projectPath, permissionMode: "bypassPermissions", model: "sonnet", userId, }, writer);In
claude-sdk.js, provider overrides only setruntimeOptions.modelwhen it is falsy, so this hardcoded"sonnet"means a provider’smodel_idis never used for commit message generation. If you want commit messages to respect provider‑configured default models (as other flows do), consider omittingmodelhere unless the caller explicitly requested one, and lettingqueryClaudeSDKapply the provider or default.Also applies to: 677-795
🧹 Nitpick comments (11)
server/routes/agent.js (1)
16-31: External agent endpoint wiring looks solid; cleanup guard could be a bit stricter
validateExternalApiKeycorrectly enforces API key auth, ties keys to users viaapiKeysDb.validateApiKey, and populatesreq.userso later logic (Claude userId, GitHub token lookup) has proper context.- The main
/handler cleanly parses boolean flags, validatesprovider, wiresreq.user.idintoqueryClaudeSDKfor Claude, and usesgithubTokensDb.getActiveGithubToken(req.user.id)for GitHub operations; the SSE vs non‑streaming writers match thequeryClaudeSDK/spawnCursorinterface.cleanupProjectonly deletes paths containing.claude/external-projects, which is a good safety net for auto‑cloned repos; if you ever allow more flexible clone locations, consider tightening this to a resolved‑path prefix check rather than a substring match, but it’s adequate for the current usage pattern.Also applies to: 439-476, 867-1351
server/database/db.js (1)
78-95: Model provider DB layer is sound; consider tightening the API surface
- The migration and
modelProvidersDbCRUD/activation behavior look correct, andgetProviders’s use ofapi_key_previewwithapi_key: undefinedis a good safeguard against leaking full secrets in API responses.getActiveProvidercurrently doesSELECT *, returning the rawapi_key. That’s fine for internal use fromclaude-sdk.js, but to reduce the chance of future handlers accidentally exposing full keys, it’s worth either:
- Narrowing the select to only needed columns in
getActiveProvider, or- Keeping
getActiveProviderinternal and enforcing that any HTTP‑facing code goes throughgetProviders.- Minor perf nit:
createProvidercallsgetProviders(userId)just to check whether this is the first provider. A lightweightSELECT COUNT(*)would avoid reading unnecessary columns.Also applies to: 372-460, 462-470
server/database/init.sql (1)
52-69: Model providers schema matches usage; consider uniqueness on provider names per userThe table structure and indexes align with
modelProvidersDb(FK, timestamps,is_activeflag, indexes onuser_idandis_active), so the schema looks sound.If you want to prevent confusing duplicates like multiple "OpenRouter" entries for the same user, consider a unique index on
(user_id, provider_name)later on.server/routes/settings.js (4)
16-93: API key routes are correct and consistent with existing patternsThe
/api-keyshandlers look good: per-user scoping viareq.user.id, masking keys in list responses, and clear error handling. Using a strict boolean check forisActiveon the toggle endpoint is also fine.If you want to harden slightly, you could:
- Add a radix to
parseInt(keyId, 10)and handleNaNexplicitly.- Optionally accept
"true"/"false"strings in addition to booleans forisActivefor more tolerant clients.
100-193: Credentials routes mirror API key behavior cleanlyThe credentials endpoints reuse the same pattern as API keys: user scoping, field validation, and not exposing
credential_valueat all (DB query already omits it), which is good from a security perspective. Toggle/delete logic is straightforward.Similar to the API keys routes, you could optionally:
- Guard
parseInt(credentialId, 10)againstNaN.- Loosen
isActivevalidation if you expect non-boolean JSON from other clients.
199-257: Model provider list/create flow looks correct; minor API-shape nitListing and creation of model providers are wired correctly:
- Providers are scoped by
req.user.id.- The first provider auto-activates in
modelProvidersDb.createProvider, and you correctly triggerupdateClaudeSettingsForProvideronly whenresult.isActiveis true.- Required fields (
providerName,apiBaseUrl,apiKey) are validated up front.One small nit:
GET /model-providersreturns snake_case fields (provider_name,api_base_url) whilePOST /model-providersreturns camelCase in theproviderpayload. The current UI ignores the create response and re-fetches, so it works, but aligning naming conventions across these responses will make the API easier to consume for other clients.
259-294: Activation route correctly updates DB and settings.jsonThe activation handler correctly:
- Calls
modelProvidersDb.setActiveProviderin a user-scoped way.- Re-reads the active provider and passes it to
updateClaudeSettingsForProvider.- Treats settings write failures as non-fatal (provider remains active in DB), which matches the "settings sync is best-effort" design.
If you want to reduce an extra round-trip from the UI, you could return the activated provider (or its id) instead of just
{ success: true }, but the current re-fetch approach is fine.src/components/CredentialsSettings.jsx (2)
52-56: Provider fetch wiring is correct; you may want to captureactiveProviderIdtooFetching
/api/settings/model-providersand storingprovidersData.providersintomodelProvidersmatches the backend response.Since the backend also returns
activeProviderId, consider capturing it in state if you ever want to distinguish between “active by DB” vs.provider.is_activelocally, or use it for client-side behaviors.
161-216: Model provider create/activate/delete logic matches the APIThe three handler functions look correct:
createModelProvidervalidates required fields, sets acreatingProviderflag, posts to the backend, and fully resets form state on success.setActiveProvideranddeleteModelProviderdelegate to the corresponding PATCH/DELETE endpoints and refresh viafetchData.A couple of small optional improvements:
- Check
res.okbeforeres.json()and surface server-side error messages to the user (e.g., toast) instead of only logging to the console.- For
deleteModelProvider, you could reuse the existing confirm text style from API keys for consistency (“Are you sure…”).server/utils/claude-settings.js (2)
10-67: Settings read/write helpers are solid; parse-error handling could be more forgivingThe combination of:
- Ensuring the
~/.claudedirectory exists before reads/writes.- Returning a sensible default
{ env: {}, permissions: { allow: [], deny: [] } }when the file is missing.- Pretty-printing JSON on write.
is all good.
Consider optionally treating JSON parse errors (corrupted
settings.json) similarly toENOENT—e.g., log the issue, fall back to the default structure, and overwrite on the next write—so a bad file doesn’t permanently break settings reads.
162-182: Backup helper is straightforward and safe
backupClaudeSettingscreates a timestamped copy next to the main file and gracefully no-ops whensettings.jsondoesn’t exist. That’s a nice utility to have before doing risky writes.You might optionally call this from higher-level flows before significant changes, but keeping it as an explicit tool is fine for now.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
server/claude-sdk.js(17 hunks)server/database/db.js(2 hunks)server/database/init.sql(1 hunks)server/index.js(3 hunks)server/routes/agent.js(33 hunks)server/routes/git.js(22 hunks)server/routes/settings.js(4 hunks)server/utils/claude-settings.js(1 hunks)src/components/CredentialsSettings.jsx(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
server/index.js (1)
server/routes/user.js (4)
userId(13-13)userId(42-42)userId(78-78)userId(93-93)
server/utils/claude-settings.js (2)
server/claude-sdk.js (2)
settings(45-49)provider(408-410)server/routes/settings.js (1)
settings(339-339)
server/database/db.js (2)
server/routes/settings.js (4)
result(40-40)result(130-136)result(226-233)providers(202-202)server/claude-sdk.js (1)
provider(408-410)
src/components/CredentialsSettings.jsx (5)
src/utils/api.js (2)
authenticatedFetch(2-21)authenticatedFetch(2-21)src/components/ApiKeysSettings.jsx (1)
fetchData(24-42)server/claude-sdk.js (1)
provider(408-410)src/components/GitPanel.jsx (1)
provider(40-42)src/components/ChatInterface.jsx (1)
provider(1702-1704)
server/routes/agent.js (3)
server/routes/git.js (2)
message(653-659)writer(707-750)server/database/db.js (3)
apiKey(224-224)apiKeysDb(215-285)githubTokensDb(354-370)server/claude-sdk.js (1)
provider(408-410)
server/routes/settings.js (2)
server/database/db.js (14)
apiKeysDb(215-285)result(132-132)result(226-226)result(268-268)result(279-279)result(293-293)result(334-334)result(345-345)result(384-384)isActive(377-377)credentialsDb(288-351)providers(404-404)modelProvidersDb(373-460)apiKey(224-224)server/utils/claude-settings.js (2)
settings(82-82)SETTINGS_PATH(10-10)
server/routes/git.js (3)
server/projects.js (6)
projectName(995-995)lines(910-910)projectPath(237-237)projectPath(390-390)project(400-407)project(473-482)server/index.js (6)
lines(1378-1378)projectPath(435-435)projectPath(1341-1341)project(441-441)userId(710-710)r(1452-1452)server/claude-sdk.js (1)
provider(408-410)
server/claude-sdk.js (1)
server/database/db.js (1)
modelProvidersDb(373-460)
🔇 Additional comments (4)
server/index.js (1)
695-705: WebSocket user propagation into Claude SDK looks consistentAttaching
request.usertowsand derivinguserIdforclaude-commandmessages is consistent with how other routes usereq.user.idand correctly enables user‑scoped provider configuration downstream. I don’t see issues here givenverifyClientalways setsinfo.req.userfor accepted connections.Also applies to: 710-711, 719-726
src/components/CredentialsSettings.jsx (2)
4-27: New model provider state and icons integrate cleanlyThe added state for model providers (
modelProviders,showNewProviderForm,newProvider*,creatingProvider) and theServericon import slot in nicely with the existing pattern for API keys and GitHub credentials. No issues here.
461-580: Model providers UI is coherent and respects backend’s maskingThe new "Model Providers" section is well-structured:
- Creation form mirrors the fields expected by the backend (name, base URL, key, optional model id and description).
- The API key visibility toggle reuses the
showTokenpattern from GitHub credentials.- The list correctly uses
provider.is_activeto show an “Active” badge and only exposesprovider.api_key_preview, keeping full keys hidden from the UI.- Actions (“Use This Provider”, delete) map to the correct handlers.
No functional issues spotted here.
server/utils/claude-settings.js (1)
184-190: Named exports are clear and match usageExporting
readClaudeSettings,writeClaudeSettings,updateClaudeSettingsForProvider,backupClaudeSettings, andSETTINGS_PATHas named exports matches how they’re imported inserver/routes/settings.jsand keeps the module easy to extend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
server/utils/claude-settings.js (1)
136-150: Consider separate configuration for small/fast model.Setting
ANTHROPIC_SMALL_FAST_MODELto the same value asANTHROPIC_MODEL(line 139) may not align with the intended purpose. The "small/fast" model typically refers to a lighter, faster model (e.g.,haiku) used for operations that don't require the full capabilities of the main model. Using the same model for both defeats this optimization.Consider either:
- Allowing providers to specify a separate
small_fast_model_idfield, or- Only setting
ANTHROPIC_MODELand leavingANTHROPIC_SMALL_FAST_MODELto use its default value.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
server/claude-sdk.js(17 hunks)server/utils/claude-settings.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/utils/claude-settings.js (2)
server/claude-sdk.js (2)
settings(45-49)provider(412-414)server/routes/settings.js (1)
settings(339-339)
server/claude-sdk.js (1)
server/database/db.js (1)
modelProvidersDb(373-460)
🔇 Additional comments (8)
server/utils/claude-settings.js (5)
16-44: LGTM: Clean implementation with proper error handling.The function correctly handles the case when settings.json doesn't exist and returns a sensible default structure. The directory creation with
recursive: trueensures the path exists before attempting to read/write.
50-67: LGTM: Straightforward implementation.The function correctly ensures the directory exists and writes formatted JSON with appropriate error handling.
84-108: Past review concerns have been addressed.The
!providerbranch now correctly clears model overrides (ANTHROPIC_MODELandANTHROPIC_SMALL_FAST_MODEL) at lines 97-103, preventing stale configurations. This addresses the concern raised in the previous review.
116-121: Good use of ANTHROPIC_AUTH_TOKEN for providers.Using
ANTHROPIC_AUTH_TOKENinstead of overwritingANTHROPIC_API_KEYcorrectly preserves any manually-set API keys while allowing provider-specific authentication.
168-188: LGTM: Robust backup implementation.The function correctly handles the case when no settings file exists and uses a filesystem-safe timestamp format for backup files.
server/claude-sdk.js (3)
15-19: LGTM: Appropriate imports for provider support.The addition of
modelProvidersDbimport enables per-user provider configuration support.
577-590: Excellent cleanup logic in finally block.The restoration logic correctly distinguishes between originally-undefined variables (which should be deleted) and originally-set variables (which should be restored). Using
originalAuthToken === undefinedis the correct check, as it distinguishesundefined(not set) fromnullor empty string (set but falsy).This ensures the environment is returned to its prior state regardless of whether the query succeeds or fails.
404-440: Environment variable isolation properly implemented and verified.The script confirms that
ANTHROPIC_AUTH_TOKENandANTHROPIC_BASE_URLare only manipulated in this single location—set during provider override (lines 418, 422) and restored in the finally block (lines 580-589). No other code paths modify these variables, confirming the isolation mechanism is effectively scoped to individual requests with proper cleanup.The capture-set-restore pattern eliminates the cross-user credential leakage risk from the previous implementation. The residual race condition during query execution is minimal and acceptable given SDK architectural constraints.
Summary
Fixes Model Provider configuration not taking effect and adds automatic synchronization with
~/.claude/settings.jsonfor Claude Code CLI compatibility.Problem
userIdparameterANTHROPIC_API_URLinstead ofANTHROPIC_BASE_URLSolution
Core Fixes:
userIdparameter to API routes (agent.js, git.js)ANTHROPIC_API_URL→ANTHROPIC_BASE_URLNew Features:
~/.claude/settings.jsonwhen creating/activating/deleting providersGET /api/settings/claude-settingsChanges
Modified:
Added:
Testing
GET /api/settings/claude-settingsshows updated configBreaking Changes
None - fully backward compatible.
Summary by CodeRabbit
New Features
Bug Fixes