Why this matters
Remote means Claude Code is reaching across the network to talk to a session somewhere else. Bridge means the local REPL stays in your terminal while a remote client watches the same work. The point of this page is not the tool names; it is the boundary each mode crosses and who owns the conversation.
One product, three boundary-crossing modes
Remote, bridge, and local REPL at a glance
The same product can stay local, move the session remote, or mirror a local REPL to a remote client.
Local REPL mode is the ordinary case: you type in one terminal, and the loop
stays there. Remote-session mode (--remote / CCR elsewhere) moves that loop
onto a separate session. Bridge mode keeps the local REPL running but mirrors
the work to a remote client that can observe and drive it.
This root page is about crossing boundaries, not just running a tool. The reading map comes next, then the exact source slices that later pages depend on.
How this part breaks down
remote-session-manager-and-ccr-lifecycleStart with the remote-session path: how Claude Code connects to a CCR session, receives SDK messages over WebSocket, and sends permission answers back across that boundary.remote-and-bridge-data-structuresRead this appendix early if the connection objects feel abstract. It introducesRemoteSessionConfig,RemoteSessionCallbacks,BridgeConfig,WorkSecret, and the bridge state labels before later pages depend on them.repl-bridge-lifecycle-and-session-creationFollow the always-on bridge path: how the REPL bridge registers an environment, creates or reconnects sessions, and keeps ingress state alive across polling and reconnects.bridge-message-flow-and-permission-roundtripStudy the message bridge itself: which inbound messages are eligible, how the bridge turns them into local SDK events, and how permission requests make a roundtrip between local UI and remote client.
The remote session contract
RemoteSessionConfig is the remote boundary in its simplest form: identify the
session, get a token, know the org, and note when the client is only viewing.
export type RemoteSessionConfig = {
sessionId: string
getAccessToken: () => string
orgUuid: string
/** True if session was created with an initial prompt that's being processed */
hasInitialPrompt?: boolean
/**
* When true, this client is a pure viewer. Ctrl+C/Escape do NOT send
* interrupt to the remote agent; 60s reconnect timeout is disabled;
* session title is never updated. Used by `claude assistant`.
*/
viewerOnly?: boolean
}
The bridge handle and state
ReplBridgeHandle is the local handle the REPL uses to write work across the
bridge. BridgeState is the small status set the UI needs to know whether the
bridge is ready, connected, reconnecting, or failed.
export type ReplBridgeHandle = {
bridgeSessionId: string
environmentId: string
sessionIngressUrl: string
writeMessages(messages: Message[]): void
writeSdkMessages(messages: SDKMessage[]): void
sendControlRequest(request: SDKControlRequest): void
sendControlResponse(response: SDKControlResponse): void
sendControlCancelRequest(requestId: string): void
sendResult(): void
teardown(): Promise<void>
}
export type BridgeState = 'ready' | 'connected' | 'reconnecting' | 'failed'
The bridge config and secret payload
BridgeConfig is the bridge-registration shape. WorkSecret is the
server-issued payload that carries the ingress token and API base URL the
bridge needs to keep polling.
export type BridgeConfig = {
dir: string
machineName: string
branch: string
gitRepoUrl: string | null
maxSessions: number
spawnMode: SpawnMode
verbose: boolean
sandbox: boolean
/** Client-generated UUID identifying this bridge instance. */
bridgeId: string
/**
* Sent as metadata.worker_type so web clients can filter by origin.
* Backend treats this as opaque — any string, not just BridgeWorkerType.
*/
workerType: string
/** Client-generated UUID for idempotent environment registration. */
environmentId: string
/**
* Backend-issued environment_id to reuse on re-register. When set, the
* backend treats registration as a reconnect to the existing environment
* instead of creating a new one. Used by `claude remote-control
* --session-id` resume. Must be a backend-format ID — client UUIDs are
* rejected with 400.
*/
reuseEnvironmentId?: string
/** API base URL the bridge is connected to (used for polling). */
apiBaseUrl: string
/** Session ingress base URL for WebSocket connections (may differ from apiBaseUrl locally). */
sessionIngressUrl: string
/** Debug file path passed via --debug-file. */
debugFile?: string
/** Per-session timeout in milliseconds. Sessions exceeding this are killed. */
sessionTimeoutMs?: number
}
WorkSecret carries the session ingress token, API base URL, source list,
and the server-driven CCR v2 selector.
export type WorkSecret = {
version: number
session_ingress_token: string
api_base_url: string
sources: Array<{
type: string
git_info?: { type: string; repo: string; ref?: string; token?: string }
}>
auth: Array<{ type: string; token: string }>
claude_code_args?: Record<string, string> | null
mcp_config?: unknown | null
environment_variables?: Record<string, string> | null
/**
* Server-driven CCR v2 selector. Set by prepare_work_secret() when the
* session was created via the v2 compat layer (ccr_v2_compat_enabled).
* Same field the BYOC runner reads at environment-runner/sessionExecutor.ts.
*/
use_code_sessions?: boolean
}