Repository navigation
feat(server): per-instance home dirs for parallel provider sign-in - #7
Merged
Merged
Conversation
Users with multiple Cursor accounts had no way to keep them signed in at the same time: every Cursor instance shared the global ~/.cursor profile and the macOS keychain credential. CursorSettings gains a homePath field exported to cursor-agent as CURSOR_CONFIG_DIR, so each provider instance can own an isolated config directory (sign-in, auth.json, CLI settings). The instance env defaults AGENT_CLI_CREDENTIAL_STORE=file so logins land in that directory instead of the shared keychain, and usage-limit credential lookup honors CURSOR_CONFIG_DIR. The model picker is then the account selector. Done with swe-2 via Devin.
Extends the Cursor profile-dir pattern to the remaining providers that keep CLI login state in a well-known location, so every provider can now hold more than one native sign-in across instances. GrokSettings, DevinSettings, and OpenCodeSettings gain a homePath field: Grok exports it as GROK_HOME; Devin and OpenCode export it as XDG_DATA_HOME, which relocates credentials.toml and auth.json. A shared mergeProviderHomePathEnvironment helper resolves the path (with ~ expansion) into the instance env; CursorHome now builds on it. Grok's usage probe and OpenCode's auth.json lookup already honor those variables. Done with swe-2 via Devin.
CURSOR_CONFIG_DIR relocates cli-config.json but never auth.json, so the previous approach left every instance sharing ~/.cursor/auth.json (and the macOS keychain). Materialize homePath as a shadow HOME instead: .cursor and .config/cursor stay real/private while every other home entry symlinks back, so cursor-agent login, config, sessions, and usage-limit credentials all resolve per account without breaking agent tools like git or ssh. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…nfig The providers sidebar rendered a default row for every known driver, so never-configured providers (Devin, Grok, OpenCode, Antigravity) cluttered the list as disabled noise. Now a default slot only lists when it is configured (dirty) or actually enabled, and the empty state offers an Add provider entry point. Default rows also get the delete affordance: deleting one resets it to factory defaults, which drops pristine slots back out of the list. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Resetting a default slot to factory config left enabled:true for Codex/Claude, so the row re-rendered and looked undeletable. Delete now also writes enabled:false and the unconfigured check ignores the enabled key, so a deleted default slot drops out of the provider list. It can be re-added via the + dialog like any provider. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The wizard is now Driver -> Identity -> Config -> Sign in. Add instance creates the instance, fires a targeted provider refresh so its auth methods arrive promptly, and lands on a sign-in step that reuses the provider card's auth sections (browser flows, paste-credential, Antigravity runtime). Navigation is capped at Config until the instance exists and locked on Sign in afterwards. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ProviderAuthSection and ProviderSetupSection grew a variant prop: panel keeps the SettingsRow two-column layout, dialog renders a stacked layout (status, method picker, action buttons, callback form) that fits the wizard popup. The wizard's sign-in step uses it so a new instance can be authenticated without the cramped settings-pane chrome. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# Conflicts: # apps/web/src/components/settings/AddProviderInstanceDialog.tsx
adampeterhiggins
marked this pull request as ready for review
September 28, 2026 17:35
A blank Home directory meant the CLI's real home, so every added Cursor, Grok, Devin, or OpenCode instance shared the default login, and signing in on one replaced it for all of them. An added instance with a blank home now gets a private directory under T3's state dir, keyed by instance id. The default instance keeps the user's existing login, and an instance environment variable that already picks the home (GROK_HOME, XDG_DATA_HOME, HOME) still wins. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POC for running multiple native logins per provider in parallel. Each provider's settings gain a
homePathfield that gives the provider instance its own credential location, so each instance can stay signed in to a different account. Since provider instances already appear as separate entries in the model picker, the picker becomes the account selector — no new auth-profile entity needed.Per-driver mapping (all fields render automatically in the instance settings form):
homePathHOME(see note)auth.json, CLI config, sessionshomePathGROK_HOMEauth.json, CLI confighomePathXDG_DATA_HOMEcredentials.tomlhomePathXDG_DATA_HOMEauth.jsonClaude (
CLAUDE_CONFIG_DIR), Codex (CODEX_HOME+ shadow home), and Antigravity already isolate per instance and are unchanged.Cursor needs a shadow HOME, not
CURSOR_CONFIG_DIR. Cursor's credential store resolvesauth.jsonfromhomedir()on macOS and$XDG_CONFIG_HOMEon Linux —CURSOR_CONFIG_DIRonly relocatescli-config.json, so a config-dir override leaves every instance sharing~/.cursor/auth.json(verified:agent aboutreports the global account under a fresh config dir). Instead,homePathis materialized as a shadow home directory:.cursorand.config/cursorstay real/private while every other home entry (~/.ssh,~/.gitconfig,~/Library, …) symlinks back to the real home, so the agent's own tools keep working. The instance runs withHOME=<shadow>plusAGENT_CLI_CREDENTIAL_STORE=filesocursor-agent loginwritesauth.jsoninto the shadow rather than the shared macOS keychain (reachable via the~/Librarysymlink). Re-materialized on each instance start to fix stale links and sync entries added to the real home later.mergeProviderHomePathEnvironmentresolveshomePath(with~expansion) into the instance env;CursorHomematerializes the shadow home and mergesHOMEaboutprobes, ACP model discovery, adapter sessions, skills probes, login/logout, usage limitscursorUsageLimitscredential lookup is home-anchored on every platform (matching the CLI), so the instanceHOMEpoints it at the rightauth.json; Grok and OpenCode probes already honorGROK_HOME/XDG_DATA_HOMEGROK_HOME,XDG_DATA_HOME,XDG_CONFIG_HOME,CURSOR_CONFIG_DIRtilde-expanded in the shared instance env merge (same asCODEX_HOME/CLAUDE_CONFIG_DIR)providers-cursor.mdcovers the shadow-home behavior; "separate accounts" sections added toproviders-devin.mdandproviders-opencode.mdOpenCode's
auth loginis an interactive TUI, so its second login is created in a terminal with the sameXDG_DATA_HOME; every other provider signs in through the instance's own browser sign-in action.Test plan
CursorHome.test.ts: env merge,~resolution, credential-store default, HOME precedence, shadow-home materialization (private dirs, shared symlinks,.configfiltering, real-home rejection)mergeProviderHomePathEnvironmenttests: empty path passthrough, multi-var injection, config-over-env precedencecursorUsageLimitstest: readsauth.jsonfrom the instanceHOMEProviderInstanceEnvironment.test.ts: tilde expansion for all six profile-dir vars/varvs/private/var)CURSOR_CONFIG_DIR=/tmp/… cursor-agent aboutstill reports the global account (proves config-dir is insufficient);HOME=/tmp/… cursor-agent aboutreports logged-out with an empty home and the correct account with anauth.jsonpresentXDG_DATA_HOME=/tmp/... devin auth statusconfirms credential path relocatesGenerated with Devin