AI Assistant

Bridge & Remote

Claude Code supports remote execution through two mechanisms: the bridge system for distributed agent execution, and direct connect for peer-to-peer session access.

Bridge Mode

Bridge mode allows Claude Code to run as a worker that polls for and executes tasks from a remote server. This enables distributed execution patterns where work is dispatched from a central coordinator.

Architecture

┌─────────────┐     poll/claim      ┌─────────────────┐
│  Bridge API  │ ◄───────────────── │  Bridge Worker   │
│   (Server)   │ ────────────────► │  (bridgeMain.ts) │
│              │    work items       │                  │
│              │ ◄───────────────── │  Session Spawner │
│              │    results          │                  │
└─────────────┘                     └─────────────────┘

Bridge Configuration

Authentication and URL resolution are centralized in bridgeConfig.ts:

// Access token: dev override first, then OAuth keychain
function getBridgeAccessToken(): string | undefined

// Base URL: dev override first, then production OAuth config
function getBridgeBaseUrl(): string
getBridgeAccessToken()string | undefined
Returns the bridge access token. Checks the dev override env var first, then falls back to OAuth keychain tokens. Returns undefined if not logged in.
getBridgeBaseUrl()string
Returns the bridge API base URL. Checks the dev override first, then uses the production OAuth config. Always returns a URL.

Bridge Main Loop

The bridgeMain.ts module implements the worker lifecycle:

1

Initialization

Validate the bridge ID, set up logging, create the API client, and initialize the token refresh scheduler.

2

Worker Registration

Register the worker with the bridge API using a work secret. The work secret encodes the session ID and is used to build SDK URLs.

3

Poll Loop

Poll for available work items with exponential backoff. Connection failures use separate backoff parameters from general errors.

4

Session Execution

When work is claimed, spawn a Claude Code session via the SessionSpawner. Sessions can run in an isolated Git worktree.

5

Result Reporting

Report session results (success, failure, timeout) back to the bridge API. Handle retries for transient failures.

Backoff Configuration

The bridge uses separate backoff strategies for connection vs. general errors:

type BackoffConfig = {
  connInitialMs: number      // 2,000ms
  connCapMs: number          // 120,000ms (2 minutes)
  connGiveUpMs: number       // 600,000ms (10 minutes)
  generalInitialMs: number   // 500ms
  generalCapMs: number       // 30,000ms
  generalGiveUpMs: number    // 600,000ms (10 minutes)
  shutdownGraceMs?: number   // SIGTERM to SIGKILL grace period
  stopWorkBaseDelayMs?: number
}

Session Spawning

The createSessionSpawner() factory creates a SessionSpawner that can:

  • Create and remove Git worktrees for isolated execution
  • Spawn sessions with configurable timeouts (default: DEFAULT_SESSION_TIMEOUT_MS)
  • Track session handles for cleanup on shutdown
type SessionSpawnOpts = { ... }
type SessionHandle = { ... }
type SpawnMode = 'worktree' | 'cwd'

JWT and Security

Bridge mode uses JWT tokens for authentication. The createTokenRefreshScheduler() handles automatic token renewal before expiration. Expired tokens trigger the isExpiredErrorType check and are treated as fatal errors requiring re-authentication.

The bridge validates its ID via validateBridgeId() and supports trusted device tokens via getTrustedDeviceToken() for additional security.

Remote Session Manager

The RemoteSessionManager manages WebSocket connections to remote Claude Code sessions:

type RemoteSessionConfig = {
  sessionId: string
  getAccessToken: () => string
  orgUuid: string
  hasInitialPrompt?: boolean
  viewerOnly?: boolean
}
sessionIdstring
Unique identifier for the remote session.
getAccessToken() => string
Function that returns the current access token.
orgUuidstring
Organization UUID for the session.
hasInitialPromptboolean
Whether the session was created with an initial prompt being processed.
viewerOnlyboolean
When true, the client is a pure viewer. Ctrl+C/Escape do not send interrupts, reconnect timeout is disabled, and session title is never updated.

Remote Session Callbacks

type RemoteSessionCallbacks = {
  onMessage: (message: SDKMessage) => void
  onPermissionRequest: (
    request: SDKControlPermissionRequest,
    requestId: string,
  ) => void
  onPermissionCancelled?: (requestId: string, toolUseId?: string) => void
  onConnected?: () => void
  onDisconnected?: () => void
}

The manager handles:

  • SDK message routing between the local UI and remote agent
  • Permission request forwarding and response relay
  • Connection lifecycle (connect, reconnect, disconnect)
  • Event delivery to the remote session via sendEventToRemoteSession()

Permission Responses

Remote permission responses use a simplified type:

type RemotePermissionResponse =
  | { behavior: 'allow'; updatedInput: Record<string, unknown> }
  | { behavior: 'deny'; message: string }

Direct Connect

Direct connect enables peer-to-peer session access over WebSocket without the bridge server intermediary:

type DirectConnectConfig = {
  serverUrl: string
  sessionId: string
  wsUrl: string
  authToken?: string
}

DirectConnectSessionManager

The DirectConnectSessionManager class manages a single WebSocket connection:

class DirectConnectSessionManager {
  constructor(config: DirectConnectConfig, callbacks: DirectConnectCallbacks)
  connect(): void
  sendMessage(content: RemoteMessageContent): void
  sendPermissionResponse(requestId: string, response: RemotePermissionResponse): void
  disconnect(): void
}
connect()void
Opens a WebSocket connection with optional auth token in headers.
sendMessage()void
Sends a message (typically user input) to the remote session.
sendPermissionResponse()void
Responds to a permission request from the remote agent.
disconnect()void
Closes the WebSocket connection.

The WebSocket receives newline-delimited JSON messages. Each message is validated as an StdoutMessage type before being dispatched to callbacks. Messages are distinguished between SDK messages (forwarded to onMessage) and control messages (permission requests forwarded to onPermissionRequest).

SDK Message Adapter

The remote system uses a type guard to distinguish SDK messages from control messages:

function isSDKMessage(message: SDKMessage | SDKControlRequest | SDKControlResponse): message is SDKMessage

Control messages include control_request, control_response, and control_cancel_request types for managing permission flows across the remote boundary.