InkdownInkdown
Start writing

Claude-Code

62 files·4 subfolders

Shared Workspace

Claude-Code
codex

07-bridge-remote

Shared from "Claude-Code" on Inkdown

Bridge & Remote Control Architecture

Overview

The Bridge system connects your local terminal to Claude.ai web interface. It enables remote control - you can interact with your local codebase from anywhere via the web.

Plain text
┌─────────────────────────────────────────────────────────────────────────────┐
│                        BRIDGE ARCHITECTURE                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────────┐              ┌──────────────────┐                  │
│  │   LOCAL TERMINAL │              │   CLAUDE.AI      │                  │
│  │                  │              │   (WEB)          │                  │
│  │  ┌────────────┐  │   WebSocket  │  ┌────────────┐  │                  │
│  │  │   Claude   │◄─┼─────────────►┼─│   Web UI   │  │                  │
│  │  │   Code CLI │  │              │  │            │  │                  │
│  │  └─────┬──────┘  │              │  └─────┬──────┘  │                  │
│  │        │         │              │        │         │                  │
│  │  ┌─────▼─────┐   │              │  ┌─────▼─────┐   │                  │
│  │  │  Bridge   │   │   REST API   │  │  Bridge   │   │                  │
│  │  │   Core    │◄──┼─────────────►├──┤  Server   │   │                  │
│  │  │ (local)   │   │              │  │  (cloud)  │   │                  │
│  │  └───────────┘   │              │  └───────────┘   │                  │
│  │                  │              │                  │                  │
│  │  ┌───────────┐   │              │                  │                  │
│  │  │   Local   │   │              │                  │                  │
│  │  │   Files   │   │              │                  │                  │
│  │  └───────────┘   │              │                  │                  │
│  └──────────────────┘              └──────────────────┘                  │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐  │
│  │                      BRIDGE MODES                                    │  │
│  │                                                                       │  │
│  │  FULL DUPLEX:  Web UI ◄──► Local CLI (bidirectional)                 │  │
│  │  OUTBOUND ONLY: Local → Web (one-way streaming)                      │  │
│  │  VIEWER ONLY:   Web → Local (read-only remote control)               │  │
│  └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
0000_start_here_index_and_recommended_reading_order.md
0100_project_overview_tech_stack_runtime_modes_and_folder_map.md
0200_startup_flow_entry_points_and_cold_start_sequence.md
0300_codebase_modules_layers_state_models_and_schemas.md
0400_system_architecture_and_design_rationale.md
0500_interactive_repl_request_flow_end_to_end.md
0600_headless_sdk_and_print_mode_request_flow_end_to_end.md
0700_mcp_integration_connection_and_tool_call_flow.md
0800_external_services_sdks_storage_and_local_dependencies.md
0900_environment_variables_settings_feature_flags_and_failure_modes.md
1000_non_obvious_patterns_gotchas_and_debugging_traps.md
1100_full_codebase_file_inventory_grouped_by_directory.md
kimi
00-overview.md
01-entrypoints.md
02-state-management.md
03-query-system.md
04-tools-system.md
05-tasks-system.md
06-ui-components.md
07-bridge-remote.md
08-services.md
09-skills-plugins.md
10-commands.md
11-testing-architecture.md
12-permission-system.md
13-build-system.md
14-ink-internals.md
15-git-internals.md
16-context-compaction.md
17-vim-mode.md
18-mailbox-notifications.md
19-session-persistence.md
20-hooks-system.md
21-error-recovery.md
README.md
qwen
00-overview.md
01-entry-points.md
02-query-engine.md
03-tools-and-tasks.md
04-commands-and-skills.md
05-state-management.md
06-ink-rendering.md
07-bridge-remote.md
08-mcp-services.md
09-services-overview.md
10-multi-agent.md
11-system-prompt-constants.md
12-tool-interface.md
13-memory-system.md
14-buddy-companion.md
15-keybindings.md
16-stop-hooks.md
17-vim-mode.md
18-upstreamproxy.md
19-cost-tracking-history.md
20-contexts-styles-onboarding.md
21-hooks.md
22-screens.md
tweets-explain
claude-code-memory-analysis.md
compact
memory-system
agentic-architecture

Core Files

FilePurpose
bridge/bridgeMain.tsEntry point for claude remote-control
bridge/remoteBridgeCore.tsCore polling and sync logic
bridge/replBridge.tsBridge-aware REPL integration
bridge/bridgeMessaging.tsMessage routing between local/web
bridge/initReplBridge.tsBridge initialization
bridge/bridgeUI.tsBridge status UI
bridge/sessionRunner.tsSession execution in bridge mode
bridge/createSession.tsSession creation API

Bridge Entry Point

TypeScript
// bridge/bridgeMain.ts
export async function bridgeMain(args: string[]): Promise<void> {
  // 1. Auth check
  const tokens = getClaudeAIOAuthTokens()
  if (!tokens?.accessToken) {
    exitWithError('Please login first: claude login')
  }

  // 2. GrowthBook gate check
  const disabledReason = await getBridgeDisabledReason()
  if (disabledReason) {
    exitWithError(`Bridge disabled: ${disabledReason}`)
  }

  // 3. Policy check
  await waitForPolicyLimitsToLoad()
  if (!isPolicyAllowed('allow_remote_control')) {
    exitWithError('Remote control disabled by organization policy')
  }

  // 4. Create environment
  const envId = await createBridgeEnvironment({
    name: args[0] || 'default',
    version: MACRO.VERSION,
  })

  // 5. Start the bridge loop
  await runBridgeLoop(envId)
}

Bridge Loop

Plain text
┌─────────────────────────────────────────────────────────────────┐
│                    BRIDGE MAIN LOOP                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────┐                                                │
│  │   START     │                                                │
│  └──────┬──────┘                                                │
│         │                                                       │
│         ▼                                                       │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐      │
│  │   POLL      │────►│  Receive    │────►│  Process    │      │
│  │   Config    │     │  Commands   │     │  Commands   │      │
│  │  (/poll)    │     │             │     │             │      │
│  └──────┬──────┘     └─────────────┘     └──────┬──────┘      │
│         │                                        │             │
│         │         ┌─────────────┐                │             │
│         │         │  Execute    │◄───────────────┘             │
│         │         │  Locally    │                              │
│         │         └──────┬──────┘                              │
│         │                │                                     │
│         │         ┌──────▼──────┐                              │
│         │         │  Stream     │                              │
│         │         │  Results    │                              │
│         │         └──────┬──────┘                              │
│         │                │                                     │
│         ▼                ▼                                     │
│  ┌─────────────┐     ┌─────────────┐                          │
│  │   SEND      │◄────│  POST       │                          │
│  │   EVENTS    │     │  Results    │                          │
│  │  (/events)  │     │             │                          │
│  └─────────────┘     └─────────────┘                          │
│                                                                 │
│  Loop continues until:                                          │
│  - User kills (Ctrl+C)                                          │
│  - Web session ends                                             │
│  - Error threshold exceeded                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Core Implementation
TypeScript
// bridge/remoteBridgeCore.ts
export async function runBridgeLoop(envId: string): Promise<void> {
  while (!shouldStop) {
    try {
      // 1. Poll for configuration (includes pending commands)
      const pollResult = await pollBridgeConfig(envId)

      // 2. Process any commands from web
      for (const command of pollResult.commands) {
        await processBridgeCommand(command, envId)
      }

      // 3. Send pending events to web
      const events = getPendingEvents()
      if (events.length > 0) {
        await sendBridgeEvents(envId, events)
        clearPendingEvents()
      }

      // 4. Heartbeat keepalive
      await sleep(pollResult.pollIntervalMs)
    } catch (error) {
      // Exponential backoff on errors
      await handleBridgeError(error)
    }
  }
}

Session Types

1. Full Bridge Session (Remote Control)
Plain text
┌─────────────────────────────────────────────────────────────┐
│  FULL BRIDGE SESSION                                        │
│                                                             │
│  Web UI on claude.ai    ◄──►    Claude Code on laptop      │
│                                                             │
│  User types in web → Command sent to local →                │
│  Local executes → Results streamed to web →                   │
│  Web shows output                                           │
│                                                             │
│  Bidirectional: Both sides can initiate                     │
└─────────────────────────────────────────────────────────────┘
2. Outbound-Only (Streaming)
TypeScript
// bridge/bridgeEnabled.ts
export function isBridgeOutboundOnly(): boolean {
  // When true, local sends events but ignores remote commands
  return config.outboundOnly ?? false
}

Use case: Share local session progress without allowing remote control.

3. Viewer Mode
TypeScript
// bridge/bridgeEnabled.ts
export function isBridgeViewerMode(): boolean {
  // When true, local executes commands but web is read-only
  return config.viewerMode ?? false
}

Use case: Let others watch your session without them controlling it.


Event Streaming

Event Types
TypeScript
// bridge/types.ts
export type BridgeEvent =
  | { type: 'message'; message: Message }
  | { type: 'tool_use'; toolUse: ToolUseBlock }
  | { type: 'tool_result'; result: ToolResultBlock }
  | { type: 'status'; status: BridgeStatus }
  | { type: 'usage'; usage: Usage }
  | { type: 'error'; error: string }
  | { type: 'heartbeat' }
Event Buffering
TypeScript
// bridge/bridgeMessaging.ts
const eventBuffer: BridgeEvent[] = []

export function queueBridgeEvent(event: BridgeEvent): void {
  eventBuffer.push(event)

  // Flush if buffer gets large
  if (eventBuffer.length > 100) {
    flushEvents()
  }
}

export async function flushEvents(): Promise<void> {
  if (eventBuffer.length === 0) return

  const events = [...eventBuffer]
  eventBuffer.length = 0

  await sendBridgeEvents(getEnvId(), events)
}

Message Routing

Plain text
┌─────────────────────────────────────────────────────────────────┐
│                    MESSAGE ROUTING                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  LOCAL SIDE                              WEB SIDE              │
│  ──────────                              ────────              │
│                                                                 │
│  ┌─────────────┐                        ┌─────────────┐       │
│  │ User types  │                        │ User types  │       │
│  │ in terminal │                        │  in browser │       │
│  └──────┬──────┘                        └──────┬──────┘       │
│         │                                       │              │
│         ▼                                       ▼              │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐          │
│  │  Local      │   │   Bridge    │   │  Remote     │          │
│  │  Handler    │◄──┤  Router     ├──►│  Handler    │          │
│  └─────────────┘   └─────────────┘   └─────────────┘          │
│         │                                       │              │
│         ▼                                       ▼              │
│  ┌─────────────┐                        ┌─────────────┐       │
│  │ Process     │                        │ Process     │       │
│  │ Locally     │                        │  Locally    │       │
│  │ (same host) │                        │  (cloud)    │       │
│  └─────────────┘                        └─────────────┘       │
│                                                                 │
│  Key: Messages flow through bridge router. Same code handles   │
│  both local and remote input, just different sources.          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Router Implementation
TypeScript
// bridge/bridgeMessaging.ts
export async function processBridgeCommand(
  command: BridgeCommand,
  envId: string
): Promise<void> {
  switch (command.type) {
    case 'submit_message':
      // Treat remote message like local input
      await handleUserInput(command.message, {
        source: 'bridge',
        envId,
      })
      break

    case 'interrupt':
      // Handle Ctrl+C from web
      abortCurrentQuery()
      break

    case 'set_permission_mode':
      // Remote mode change
      updatePermissionMode(command.mode)
      break

    case 'approve_tool':
      // Approve pending tool from web
      resolvePendingPermission(true)
      break
  }
}

Daemon Mode

For persistent remote sessions, Claude Code can run as a daemon:

Plain text
┌─────────────────────────────────────────────────────────────────┐
│                    DAEMON ARCHITECTURE                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    DAEMON SUPERVISOR                     │   │
│  │                    (daemon/main.ts)                      │   │
│  │                                                         │   │
│  │  Manages:                                               │   │
│  │  - Session registry (~/.claude/sessions/)               │   │
│  │  - Worker process lifecycle                             │   │
│  │  - Health checks                                        │   │
│  │  - Log rotation                                           │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                  │
│          ┌───────────────────┼───────────────────┐              │
│          │                   │                   │              │
│          ▼                   ▼                   ▼              │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐         │
│  │  Worker 1    │   │  Worker 2    │   │  Worker N    │         │
│  │  (session)   │   │  (session)   │   │  (session)   │         │
│  │              │   │              │   │              │         │
│  │ ┌──────────┐ │   │ ┌──────────┐ │   │ ┌──────────┐ │         │
│  │ │ Bridge   │ │   │ │ Bridge   │ │   │ │ Bridge   │ │         │
│  │ │ Enabled  │ │   │ │ Enabled  │ │   │ │ Enabled  │ │         │
│  │ └──────────┘ │   │ └──────────┘ │   │ └──────────┘ │         │
│  └──────────────┘   └──────────────┘   └──────────────┘         │
│                                                                 │
│  Commands: claude ps, claude logs, claude kill                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Daemon Commands
Bash
# List sessions
$ claude ps
ID          STATUS    BRIDGE    AGE
abc123      active    online    2h
def456      idle      offline   5m

# View logs
$ claude logs abc123
[2024-...] User: Fix the bug
[2024-...] Assistant: I'll help...

# Attach to session
$ claude attach abc123
(attaches to running session)

# Kill session
$ claude kill abc123
(session terminated)

Bridge Configuration

Config Files
TypeScript
// bridge/bridgeConfig.ts
export type BridgeConfig = {
  // Connection
  envId: string
  serverUrl: string
  pollIntervalMs: number

  // Modes
  outboundOnly: boolean
  viewerMode: boolean

  // Features
  enableSync: boolean
  enableFileWatch: boolean
}

// Stored in ~/.claude/bridge.json
export function loadBridgeConfig(): BridgeConfig {
  return readJsonFile('~/.claude/bridge.json')
}
Environment Variables
Bash
# Bridge configuration
CLAUDE_CODE_BRIDGE_URL=https://claude.ai/bridge
CLAUDE_CODE_BRIDGE_ENV=my-laptop
CLAUDE_CODE_BRIDGE_OUTBOUND_ONLY=1

# Daemon configuration
CLAUDE_CODE_DAEMON_ENABLED=1
CLAUDE_CODE_DAEMON_LOG_LEVEL=debug

Security

Authentication
TypeScript
// bridge/jwtUtils.ts
export function verifyBridgeToken(token: string): JWTPayload {
  const payload = jwt.verify(token, getPublicKey())

  // Validate claims
  if (payload.aud !== 'bridge') {
    throw new Error('Invalid audience')
  }
  if (payload.exp < Date.now() / 1000) {
    throw new Error('Token expired')
  }

  return payload
}
Permission Enforcement
TypeScript
// bridge/bridgePermissionCallbacks.ts
export const bridgePermissionCallbacks: BridgePermissionCallbacks = {
  async onToolRequest(tool, input) {
    // In bridge mode, dangerous tools need web approval
    if (tool.permissionLevel === 'dangerous') {
      const approval = await requestWebApproval(tool, input)
      return approval
    }
    return { allowed: true }
  },

  async onFileAccess(path) {
    // Respect .gitignore and .claudeignore
    if (isIgnoredPath(path)) {
      return { allowed: false, reason: 'Path is ignored' }
    }
    return { allowed: true }
  },
}

UI Indicators

Bridge Status in Footer
Plain text
┌────────────────────────────────────────────────────────────────┐
│ >                                                               │
│                                                                 │
│ [Ready] Bridge: connected  │  Tasks: 2  │  Model: Claude 4.6    │
└────────────────────────────────────────────────────────────────┘
Status States
StateIndicatorMeaning
offlineNothingBridge not enabled
connecting"Bridge: ..."Connecting to server
connected"Bridge: ready"Connected, no active session
active"Bridge: active"Web user connected
error"Bridge: error"Connection failed
reconnecting"Bridge: ..."Reconnecting after drop

Key Concepts

1. Bridge is Optional

Bridge is a feature flag. Most users never enable it.

2. Poll-Based with WebSocket Upgrade

Primary transport is HTTP polling. WebSocket used when available for lower latency.

3. Event Sourcing

All state changes are events. Events are queued and flushed to remote.

4. Same Code, Different Input Source

Remote commands go through the same code path as local input.

5. Graceful Degradation

If bridge fails, local session continues unaffected.


Troubleshooting

Bridge Won't Connect
Bash
# Check auth
claude login

# Check GrowthBook gate
claude config get bridge_mode

# Check policy
claude doctor

# Verbose logging
claude remote-control --verbose
Session Out of Sync
Bash
# Force reconnect
Ctrl+\ (send SIGQUIT to reset)

# Or restart bridge
Ctrl+C
claude remote-control

Related Documentation

  • Entrypoints - How bridge mode starts
  • State Management - How state syncs
  • Query System - How remote queries work