Why this matters
The REPL bridge is the always-on side of remote control. It starts with environment registration, then a fresh session creation on first launch, then polling for work so claude.ai can hand it a live conversation. When work shows up, the bridge opens the ingress connection, moves messages through that connection, and keeps a reconnect path ready if the transport or environment drops out.
That is one lifecycle, not a pile of separate helper calls. When the bridge is resuming saved state, perpetual mode and the saved bridge pointer can let it reuse the old session instead of creating a new one. The bridge is trying to stay attached to the same conversation while the network, the server, or the process itself gets interrupted.
The bridge lifecycle, in plain English
The bridge does a few things in order:
- Register a bridge environment so the server knows where this local REPL session lives.
- Create a session on a fresh start. If the bridge is resuming saved state, it can reuse the old session instead, but only when perpetual mode and the saved bridge pointer line up.
- Poll for work so the server can hand back a session ingress token when a remote user types.
- Open ingress and stream messages once that token arrives.
- Fall back to reconnecting state if the transport drops or the environment is reaped, then try again without losing the session if possible.
The state labels in replBridge.ts reflect that shape: ready before the
bridge is attached, connected when ingress is live, reconnecting while
it is trying to recover, and failed when it gives up.
What initBridgeCore() needs before it can start
initBridgeCore() is the bootstrap-free version of the bridge. It does not
read bootstrap/state or sessionStorage; the caller has to hand it the
context it needs up front.
At minimum, that context includes:
- where the worktree or repo lives (
dir,branch,gitRepoUrl) - the machine and bridge identity (
machineName,workerType) - the API and ingress URLs (
baseUrl,sessionIngressUrl) - a way to read the current access token (
getAccessToken) - functions for session creation and archival (
createSession,archiveSession) - the session title to use now, plus an optional refresher for reconnects
(
title,getCurrentTitle)
It can also accept message callbacks, permission callbacks, polling config, and the optional initial replay state. Those are bridge behavior details, not prerequisites for starting the lifecycle itself.
The source shapes behind the lifecycle
The handle, state, entry point, core params, and poll recovery constants are the parts that explain how the lifecycle fits together.
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'
ReplBridgeHandle is the outward-facing object the caller keeps hold of.
It carries the session and environment IDs, exposes the ingress URL, and
lets the caller write messages or shut the bridge down cleanly.
export async function initBridgeCore(
params: BridgeCoreParams,
): Promise<BridgeCoreHandle | null> {
initBridgeCore() is the bootstrap-free entry point. Once the caller has
supplied the starting context, it can register the environment, create a
fresh session, or resume saved state before it enters the poll and ingress
loop.
export type BridgeCoreParams = {
dir: string
machineName: string
branch: string
gitRepoUrl: string | null
title: string
baseUrl: string
sessionIngressUrl: string
/**
* Opaque string sent as metadata.worker_type. Use BridgeWorkerType for
* the two CLI-originated values; daemon callers may send any string the
* backend recognizes (it's just a filter key on the web side).
*/
workerType: string
getAccessToken: () => string | undefined
This excerpt stays on the startup prerequisites: repo/worktree, identity,
URLs, worker type, and the access-token provider. The full BridgeCoreParams
surface also carries session creation, archival, and later callbacks, but
those sit beyond the narrow startup slice shown here.
const POLL_ERROR_INITIAL_DELAY_MS = 2_000
const POLL_ERROR_MAX_DELAY_MS = 60_000
const POLL_ERROR_GIVE_UP_MS = 15 * 60 * 1000
Those constants describe the recovery loop when polling starts failing. The bridge waits a little at first, backs off up to a minute, and only gives up after a very long window because the server is still the authority on when a session is really dead.