feat(server): multi-instance support + Claude Remote Control launch#2999
feat(server): multi-instance support + Claude Remote Control launch#2999ReconGrunt wants to merge 1 commit into
Conversation
Run multiple T3 server instances on one PC (Cursor-style), and launch the official Claude Code Remote Control feature from T3.
Multi-instance:
- InstanceRegistry: per-instance JSON lock files under a shared ~/.t3/instances dir with dead-pid pruning (announce/withdraw/list).
- 't3 instances' command; 't3 start --instance <name>' derives an isolated per-instance baseDir (explicit --base-dir/T3CODE_HOME still wins).
- server.ts announces on startup / withdraws on shutdown (failure-isolated).
Claude Remote Control (CLI-only; not exposed by the Agent SDK T3 normally uses to drive Claude):
- ClaudeRemoteControlLauncher spawns the real claude binary in RC mode ('claude remote-control' / '--remote-control'), reusing makeClaudeEnvironment for HOME/account selection.
- 't3 remote-control' / 'rc' command; the session is then driven from the Claude iOS/web app via Anthropic's relay (no custom relay built).
Docs: multi-instance.md, remote-control.md, web-surfaces-spec.md + provider/runtime/remote-access updates. Session record under AGENT_SWARM.md, swarm/, SPRINT_1_DELIVERABLE.md.
Verified on Windows: server package typecheck green; 14/14 new unit tests pass. Desktop multi-window and in-app PTY launch are specced for a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6b49e6f. Configure here.
| tailscaleServePort, | ||
| // Only present when `--instance` was supplied (omitted otherwise so existing strict | ||
| // config equality assertions are unaffected). server.ts reads this to announce the instance. | ||
| ...(instanceName !== undefined ? { instanceName } : {}), |
There was a problem hiding this comment.
Invalid instance skips isolation
High Severity
When --instance is passed but sanitizeInstanceName yields no slug (whitespace-only or punctuation-only names), baseDir falls back to the default shared root while instanceName is still set from the raw flag. The server then advertises a named instance that uses the same SQLite and state as the default instance, breaking the isolation --instance is meant to provide.
Reviewed by Cursor Bugbot for commit 6b49e6f. Configure here.
| .toLowerCase() | ||
| .replace(/[^a-z0-9._-]+/g, "-") | ||
| .replace(/^[-_.]+|[-_.]+$/g, ""); | ||
| return slug.length > 0 ? slug : undefined; |
There was a problem hiding this comment.
Distinct names share instance directory
Medium Severity
sanitizeInstanceName maps different --instance values to the same slug (for example names that differ only by characters stripped or replaced with -), so separate starts can resolve to the identical instances-data path and share one database without warning.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 6b49e6f. Configure here.
| mode, | ||
| ...(Option.isSome(flags.name) ? { name: flags.name.value } : {}), | ||
| ...(Option.isSome(flags.cwd) ? { cwd: flags.cwd.value } : {}), | ||
| }); |
There was a problem hiding this comment.
Remote control cwd not expanded
Low Severity
The optional cwd argument for t3 remote-control is passed straight to the child process without expandHomePath or path.resolve, unlike server startup in resolveServerConfig, so paths like ~/projects/app may not be used as the working directory.
Reviewed by Cursor Bugbot for commit 6b49e6f. Configure here.
| | Flag | Description | | ||
| | --- | --- | | ||
| | `--claude-home <path>` | Path to the Claude HOME directory. Defaults to the home directory of the active Claude provider. | | ||
| | `--account <path>` | Alias for `--claude-home`. | |
There was a problem hiding this comment.
🟢 Low user/remote-control.md:63
Line 63 documents --account <path> as an alias for --claude-home, but the CLI implementation in apps/server/src/cli/remoteControl.ts only defines --claude-home via Flag.string("claude-home")). The --account flag does not exist, so users who follow this documentation and pass --account will receive an unrecognized flag error. Either add --account as an alias in the implementation, or remove it from the documentation.
- | `--account <path>` | Alias for `--claude-home`. |Also found in 1 other location(s)
docs/architecture/multi-instance.md:167
The documentation contradicts itself about where the single-instance lock enforcement lives. Line 167 states "Single-instance lock is in
apps/desktop/src/electron/ElectronApp.ts", but lines 178-179 correctly state "The single-instance enforcement lives inapps/desktop/src/app/DesktopCloudAuth.ts". The codebase confirms thatElectronApp.tsonly wraps the Electron API as a method, whileDesktopCloudAuth.ts:293is whererequestSingleInstanceLockis actually called to enforce single-instance behavior. The project's own swarm notes (swarm/ATLAS.md,swarm/HELM.md) explicitly corrected this: "requestSingleInstanceLocklives inDesktopCloudAuth.ts, notElectronApp.ts". A developer following line 167 would look in the wrong file.
🤖 Copy this AI Prompt to have your agent fix this:
In file @docs/user/remote-control.md around line 63:
Line 63 documents `--account <path>` as an alias for `--claude-home`, but the CLI implementation in `apps/server/src/cli/remoteControl.ts` only defines `--claude-home` via `Flag.string("claude-home"))`. The `--account` flag does not exist, so users who follow this documentation and pass `--account` will receive an unrecognized flag error. Either add `--account` as an alias in the implementation, or remove it from the documentation.
Also found in 1 other location(s):
- docs/architecture/multi-instance.md:167 -- The documentation contradicts itself about where the single-instance lock enforcement lives. Line 167 states "Single-instance lock is in `apps/desktop/src/electron/ElectronApp.ts`", but lines 178-179 correctly state "The single-instance enforcement lives in `apps/desktop/src/app/DesktopCloudAuth.ts`". The codebase confirms that `ElectronApp.ts` only *wraps* the Electron API as a method, while `DesktopCloudAuth.ts:293` is where `requestSingleInstanceLock` is actually called to enforce single-instance behavior. The project's own swarm notes (`swarm/ATLAS.md`, `swarm/HELM.md`) explicitly corrected this: "`requestSingleInstanceLock` lives in `DesktopCloudAuth.ts`, not `ElectronApp.ts`". A developer following line 167 would look in the wrong file.
| } catch (error) { | ||
| const code = (error as NodeJS.ErrnoException | undefined)?.code; | ||
| // ESRCH → no such process (dead). EPERM → exists but not ours (alive). | ||
| return code === "EPERM"; |
There was a problem hiding this comment.
🟡 Medium instances/InstanceRegistry.ts:77
isPidAlive returns false for any error code other than "EPERM", so unexpected errors (neither "ESRCH" nor "EPERM") incorrectly treat the process as dead and cause it to be pruned. This contradicts the docstring which states "Any other outcome is treated conservatively as alive." Consider changing the condition to return code !== "ESRCH" so unknown errors are treated as alive.
- return code === "EPERM";
+ return code !== "ESRCH";🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/instances/InstanceRegistry.ts around line 77:
`isPidAlive` returns `false` for any error code other than `"EPERM"`, so unexpected errors (neither `"ESRCH"` nor `"EPERM"`) incorrectly treat the process as dead and cause it to be pruned. This contradicts the docstring which states "Any other outcome is treated conservatively as alive." Consider changing the condition to `return code !== "ESRCH"` so unknown errors are treated as alive.
Evidence trail:
apps/server/src/instances/InstanceRegistry.ts lines 57-79 (REVIEWED_COMMIT): docstring at lines 60-62 states 'Any other outcome is treated conservatively as alive so we never prune a live instance by mistake.' but line 77 `return code === "EPERM"` returns false for any error code that is not EPERM, including unexpected errors that are neither ESRCH nor EPERM.
|
|
||
| --- | ||
|
|
||
| ## Connecting to a Named Instance |
There was a problem hiding this comment.
🟡 Medium architecture/multi-instance.md:141
The "Connecting to a Named Instance" section describes running t3 start --instance work twice to "connect a second client", but this actually launches a second independent server process that binds to a different port while sharing the same baseDir and SQLite database. Two concurrent server processes accessing the same SQLite database violates the isolation guarantee from lines 20-21 and can cause database locking errors or data corruption. Consider clarifying that this pattern requires a client-only reconnect mode, or document that concurrent server processes on the same baseDir are unsafe.
🤖 Copy this AI Prompt to have your agent fix this:
In file @docs/architecture/multi-instance.md around line 141:
The "Connecting to a Named Instance" section describes running `t3 start --instance work` twice to "connect a second client", but this actually launches a second independent server process that binds to a different port while sharing the same `baseDir` and SQLite database. Two concurrent server processes accessing the same SQLite database violates the isolation guarantee from lines 20-21 and can cause database locking errors or data corruption. Consider clarifying that this pattern requires a client-only reconnect mode, or document that concurrent server processes on the same `baseDir` are unsafe.
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. This PR introduces two major new features (multi-instance support and Claude Remote Control launch) with significant new runtime behavior. Unresolved review comments identify a high-severity bug where invalid instance names can break data isolation, plus medium-severity issues with name collisions and process liveness detection logic. You can customize Macroscope's approvability policy. Learn more. |


Run multiple T3 server instances on one PC (Cursor-style), and launch the official Claude Code Remote Control feature from T3.
Multi-instance:
Claude Remote Control (CLI-only; not exposed by the Agent SDK T3 normally uses to drive Claude):
Docs: multi-instance.md, remote-control.md, web-surfaces-spec.md + provider/runtime/remote-access updates. Session record under AGENT_SWARM.md, swarm/, SPRINT_1_DELIVERABLE.md.
Verified on Windows: server package typecheck green; 14/14 new unit tests pass. Desktop multi-window and in-app PTY launch are specced for a follow-up.
What Changed
Why
UI Changes
Checklist
Note
Medium Risk
Changes server startup lifecycle and spawns external
claudeprocesses with shared on-disk registry state; mitigated by failure-isolated registry I/O and mostly additive code with new tests.Overview
Adds multi-instance support on one machine and a
t3 remote-control/rcpath to launch the official Claude CLI in Remote Control mode (not the Agent SDK).Multi-instance: New
InstanceRegistrywrites shared JSON lock files under~/.t3/instances, prunes dead PIDs on read, and is hooked intoserver.tsvia a failure-isolated announce/withdraw layer. CLI gains--instance <name>(isolatedinstances-data/<name>base dir when--base-dir/T3CODE_HOMEare unset), optionalinstanceNameon server config, andt3 instances(table or--json).bin.tsregistersinstancesandremote-controlsubcommands.Remote Control: New launcher spawns
claude remote-controlorclaude --remote-controlwithmakeClaudeEnvironmentfor HOME, inherited stdio, and typed spawn/exit errors; unit tests mock the spawner.Docs / session artifacts: Architecture and user guides for multi-instance and RC, UI ASCII specs, provider/runtime cross-links, plus
AGENT_SWARM.md, agent logs, andSPRINT_1_DELIVERABLE.md. Deferred (spec only): Electron multi-window and in-app PTY launch for RC.Reviewed by Cursor Bugbot for commit 6b49e6f. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add multi-instance support and
t3 remote-controlCLI command to the server~/.t3/instances/) that announces/withdraws JSON lock files on server start/shutdown, enabling multiple named server instances to coexist with isolated base directories.--instance <name>flag tot3 startthat derives a per-instance base directory under<defaultBaseRoot>/instances-data/<name>when no explicit--base-diris set.t3 instancescommand that lists live server instances in human-readable or JSON form by reading and pruning the shared registry.t3 remote-control(t3 rc) command that launches theclaudeCLI in Remote Control mode, with mutually exclusive--interactive/--servermode flags and optional--nameand--claude-homesettings.📊 Macroscope summarized 6b49e6f. 21 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.