Skip to content

feat(server): multi-instance support + Claude Remote Control launch#2999

Open
ReconGrunt wants to merge 1 commit into
pingdotgg:mainfrom
ReconGrunt:feature/multi-instance-and-claude-remote-control
Open

feat(server): multi-instance support + Claude Remote Control launch#2999
ReconGrunt wants to merge 1 commit into
pingdotgg:mainfrom
ReconGrunt:feature/multi-instance-and-claude-remote-control

Conversation

@ReconGrunt

@ReconGrunt ReconGrunt commented Jun 8, 2026

Copy link
Copy Markdown

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

What Changed

Why

UI Changes

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Changes server startup lifecycle and spawns external claude processes 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 / rc path to launch the official Claude CLI in Remote Control mode (not the Agent SDK).

Multi-instance: New InstanceRegistry writes shared JSON lock files under ~/.t3/instances, prunes dead PIDs on read, and is hooked into server.ts via a failure-isolated announce/withdraw layer. CLI gains --instance <name> (isolated instances-data/<name> base dir when --base-dir / T3CODE_HOME are unset), optional instanceName on server config, and t3 instances (table or --json). bin.ts registers instances and remote-control subcommands.

Remote Control: New launcher spawns claude remote-control or claude --remote-control with makeClaudeEnvironment for 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, and SPRINT_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-control CLI command to the server

  • Adds an instance registry (~/.t3/instances/) that announces/withdraws JSON lock files on server start/shutdown, enabling multiple named server instances to coexist with isolated base directories.
  • Introduces a --instance <name> flag to t3 start that derives a per-instance base directory under <defaultBaseRoot>/instances-data/<name> when no explicit --base-dir is set.
  • Adds a t3 instances command that lists live server instances in human-readable or JSON form by reading and pruning the shared registry.
  • Adds a t3 remote-control (t3 rc) command that launches the claude CLI in Remote Control mode, with mutually exclusive --interactive/--server mode flags and optional --name and --claude-home settings.
  • Adds architecture and user-facing docs covering multi-instance isolation, the instance registry, Remote Control usage, and the distinction from Remote Access.
📊 Macroscope summarized 6b49e6f. 21 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

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

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 056dc608-f77b-476a-9138-a9040c9a95c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jun 8, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ 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 } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6b49e6f. Configure here.

.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "-")
.replace(/^[-_.]+|[-_.]+$/g, "");
return slug.length > 0 ? slug : undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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 } : {}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

🤖 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";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@macroscopeapp

macroscopeapp Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant