Skip to content

Conversation

@DawnLck
Copy link

@DawnLck DawnLck commented Nov 19, 2025

Summary

Fixes Model Provider configuration not taking effect and adds automatic synchronization with ~/.claude/settings.json for Claude Code CLI compatibility.

Problem

  • Model Provider settings were ignored due to missing userId parameter
  • Used wrong environment variable ANTHROPIC_API_URL instead of ANTHROPIC_BASE_URL
  • Settings.json file was never updated, causing CLI configuration mismatch

Solution

Core Fixes:

  • Added userId parameter to API routes (agent.js, git.js)
  • Fixed environment variable: ANTHROPIC_API_URLANTHROPIC_BASE_URL
  • Added debug logging for troubleshooting

New Features:

  • Auto-sync ~/.claude/settings.json when creating/activating/deleting providers
  • New utility module for settings management
  • Debug endpoint: GET /api/settings/claude-settings

Changes

Modified:

  • [server/claude-sdk.js] - Fixed env var, added logging
  • [server/routes/agent.js] Added userId parameter
  • [server/routes/git.js]- Added userId parameter
  • [server/routes/settings.js]- Added settings.json sync

Added:

  • [server/utils/claude-settings.js]- Settings management utility

Testing

  1. Create a Model Provider via API or UI
  2. Check GET /api/settings/claude-settings shows updated config
  3. Verify server logs show provider activation
  4. Start new conversation to confirm provider is used

Breaking Changes

None - fully backward compatible.

Summary by CodeRabbit

  • New Features

    • Model provider management in Settings: add, edit, activate, and delete providers (name, base URL, API key, default model, description).
    • Use and switch providers per user; chat and AI actions respect the active provider.
    • CLI settings sync and automatic settings backup when providers change.
  • Bug Fixes

    • Credentials/restoration fixes to prevent cross-request credential leakage.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Database Layer
server/database/db.js, server/database/init.sql
New model_providers table, indexes, and a modelProvidersDb export with methods: createProvider, getProviders, getActiveProvider, setActiveProvider, deleteProvider.
Core SDK Integration
server/claude-sdk.js
Apply active provider overrides at runtime when options.userId is present: set ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, and provider-specified model before SDK mapping; restore env vars in finally block. Minor logging/string formatting tweaks.
Server WebSocket & Routing
server/index.js, server/routes/agent.js, server/routes/git.js
Attach authenticated user to WebSocket (ws.user) and thread userId into Claude session and AI commit-message flows; generateCommitMessageWithAI signature extended to accept userId. Minor middleware flow and string formatting changes.
Settings Management
server/routes/settings.js, server/utils/claude-settings.js
New API endpoints for model providers (list/create/activate/delete) and utilities to read/write/backup ~/.claude/settings.json, plus updateClaudeSettingsForProvider to sync provider overrides into settings.json.
Frontend UI
src/components/CredentialsSettings.jsx
Adds Model Providers UI: form to create provider (name, base URL, API key, default model, description), list of providers, activate/delete actions, and integration with backend endpoints.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay close attention to:
    • server/database/init.sql and server/database/db.js for FK/index correctness and activation semantics.
    • server/utils/claude-settings.js for file I/O edge cases, atomic writes, and backup behavior.
    • server/claude-sdk.js for env var restoration, ordering of provider overrides vs. SDK option mapping, and potential cross-request leakage.
    • WebSocket user-threading in server/index.js and plumbing of userId in routes (rpc/agent/git).
    • Frontend validation and secret handling in src/components/CredentialsSettings.jsx.

Possibly related PRs

Suggested reviewers

  • viper151

Poem

🐰
I nibbled keys and hopped through code,
A provider here, a setting stowed.
Per-user keys, a model bright,
From DB burrow to Claude's light.
Hooray — I’ll hop and celebrate tonight! 🥕

Pre-merge checks and finishing touches

✅ 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 accurately describes the main changes: adding Model Provider configuration sync to settings.json and addressing activation issues.
Docstring Coverage ✅ Passed Docstring coverage is 96.67% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Contributor

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

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 model

The /generate-commit-message route and generateCommitMessageWithAI correctly propagate userId into queryClaudeSDK, so the active provider’s API key/base URL are honored. However, in the Claude branch you always pass model: "sonnet":

await queryClaudeSDK(prompt, {
  cwd: projectPath,
  permissionMode: "bypassPermissions",
  model: "sonnet",
  userId,
}, writer);

In claude-sdk.js, provider overrides only set runtimeOptions.model when it is falsy, so this hardcoded "sonnet" means a provider’s model_id is never used for commit message generation. If you want commit messages to respect provider‑configured default models (as other flows do), consider omitting model here unless the caller explicitly requested one, and letting queryClaudeSDK apply 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

  • validateExternalApiKey correctly enforces API key auth, ties keys to users via apiKeysDb.validateApiKey, and populates req.user so later logic (Claude userId, GitHub token lookup) has proper context.
  • The main / handler cleanly parses boolean flags, validates provider, wires req.user.id into queryClaudeSDK for Claude, and uses githubTokensDb.getActiveGithubToken(req.user.id) for GitHub operations; the SSE vs non‑streaming writers match the queryClaudeSDK/spawnCursor interface.
  • cleanupProject only 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 modelProvidersDb CRUD/activation behavior look correct, and getProviders’s use of api_key_preview with api_key: undefined is a good safeguard against leaking full secrets in API responses.
  • getActiveProvider currently does SELECT *, returning the raw api_key. That’s fine for internal use from claude-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 getActiveProvider internal and enforcing that any HTTP‑facing code goes through getProviders.
  • Minor perf nit: createProvider calls getProviders(userId) just to check whether this is the first provider. A lightweight SELECT 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 user

The table structure and indexes align with modelProvidersDb (FK, timestamps, is_active flag, indexes on user_id and is_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 patterns

The /api-keys handlers look good: per-user scoping via req.user.id, masking keys in list responses, and clear error handling. Using a strict boolean check for isActive on the toggle endpoint is also fine.

If you want to harden slightly, you could:

  • Add a radix to parseInt(keyId, 10) and handle NaN explicitly.
  • Optionally accept "true"/"false" strings in addition to booleans for isActive for more tolerant clients.

100-193: Credentials routes mirror API key behavior cleanly

The credentials endpoints reuse the same pattern as API keys: user scoping, field validation, and not exposing credential_value at 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) against NaN.
  • Loosen isActive validation if you expect non-boolean JSON from other clients.

199-257: Model provider list/create flow looks correct; minor API-shape nit

Listing 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 trigger updateClaudeSettingsForProvider only when result.isActive is true.
  • Required fields (providerName, apiBaseUrl, apiKey) are validated up front.

One small nit: GET /model-providers returns snake_case fields (provider_name, api_base_url) while POST /model-providers returns camelCase in the provider payload. 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.json

The activation handler correctly:

  • Calls modelProvidersDb.setActiveProvider in 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 capture activeProviderId too

Fetching /api/settings/model-providers and storing providersData.providers into modelProviders matches 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_active locally, or use it for client-side behaviors.


161-216: Model provider create/activate/delete logic matches the API

The three handler functions look correct:

  • createModelProvider validates required fields, sets a creatingProvider flag, posts to the backend, and fully resets form state on success.
  • setActiveProvider and deleteModelProvider delegate to the corresponding PATCH/DELETE endpoints and refresh via fetchData.

A couple of small optional improvements:

  • Check res.ok before res.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 forgiving

The combination of:

  • Ensuring the ~/.claude directory 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 to ENOENT—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

backupClaudeSettings creates a timestamped copy next to the main file and gracefully no-ops when settings.json doesn’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

📥 Commits

Reviewing files that changed from the base of the PR and between e952cf0 and 5c261ae.

📒 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 consistent

Attaching request.user to ws and deriving userId for claude-command messages is consistent with how other routes use req.user.id and correctly enables user‑scoped provider configuration downstream. I don’t see issues here given verifyClient always sets info.req.user for accepted connections.

Also applies to: 710-711, 719-726

src/components/CredentialsSettings.jsx (2)

4-27: New model provider state and icons integrate cleanly

The added state for model providers (modelProviders, showNewProviderForm, newProvider*, creatingProvider) and the Server icon 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 masking

The 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 showToken pattern from GitHub credentials.
  • The list correctly uses provider.is_active to show an “Active” badge and only exposes provider.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 usage

Exporting readClaudeSettings, writeClaudeSettings, updateClaudeSettingsForProvider, backupClaudeSettings, and SETTINGS_PATH as named exports matches how they’re imported in server/routes/settings.js and keeps the module easy to extend.

Copy link
Contributor

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

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_MODEL to the same value as ANTHROPIC_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:

  1. Allowing providers to specify a separate small_fast_model_id field, or
  2. Only setting ANTHROPIC_MODEL and leaving ANTHROPIC_SMALL_FAST_MODEL to use its default value.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c261ae and 32cc80d.

📒 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: true ensures 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 !provider branch now correctly clears model overrides (ANTHROPIC_MODEL and ANTHROPIC_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_TOKEN instead of overwriting ANTHROPIC_API_KEY correctly 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 modelProvidersDb import 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 === undefined is the correct check, as it distinguishes undefined (not set) from null or 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_TOKEN and ANTHROPIC_BASE_URL are 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.

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