AI Agents

深度拆解:Claude Code Remote Control 到底怎麼運作的

逆向工程 Claude Code /remote-control 背後的 relay 協定、heartbeat、重連機制與安全模型。

深度拆解:Claude Code Remote Control 到底怎麼運作的
Chen-Hung WuChen-Hung WuFeb 26, 2026
閱讀時間約 20 分鐘

這東西不應該能動才對

兩天前 Anthropic 推了一個功能:在筆電上開一個 Claude Code session,然後用手機接手操作。不用 SSH、不用 port forwarding。掃個 QR code 就直接進去了。

我第一個反應是「酷喔」。第二個反應是「等等 - 怎麼做到的?

你的筆電在 NAT 後面。手機掛在 LTE 上。沒有共用網路、沒有 VPN。但你在 iPhone 上打的指令,卻能讓家裡桌上那台 MacBook 跑 git diff

我花了兩天翻官方文件、GitHub issues、bug reports、第三方安全稽核報告,還有 Hacker News 上的討論串,把這東西拆開來看。以下是我找到的東西。


連線架構

Connection Architecture

零 Inbound Port

整個設計建立在一個限制上:你的機器永遠不會開任何 listening port。一個都沒有。官方文件講得很直白:

"Your local Claude Code session makes outbound HTTPS requests only and never opens inbound ports on your machine."

如果你用過 Tailscale,你已經知道這個招了。Tailscale 的 DERP relay server 就是同樣的做法 - 兩端都往外連到 relay,然後 relay 把它們接在一起。Claude Code 做的事一模一樣,只是 relay 的是 application message 而不是 network packet。

Relay 就在 Anthropic API 裡面

三個角色:

你的機器 — 就是 Claude Code 的 CLI process。能完整存取你的檔案系統、SSH key、.env、git repo。所有程式碼執行都發生在這裡,其他地方都不會。

api.anthropic.com — 扮演 message relay 跟 session router 的角色。它在兩端之間轉發聊天訊息跟 tool result。它不會存你的 source code。只有 conversation message 會經過。

手機/瀏覽器claude.ai/code 或 Claude 手機 app。純 UI。渲染對話、送出 prompt。這裡不跑任何程式碼。

協定

從官方文件跟 bug report 拼湊出來的:

  • CLI → Anthropic:HTTPS polling。CLI 每隔幾秒問一次「有新訊息嗎?」
  • Anthropic → CLI:用 SSE(Server-Sent Events)串流回 tool result 跟 assistant message - 跟標準 Claude API 做 streaming 的機制一樣。
  • 手機 → Anthropic:一般的 HTTPS + SSE,跟 claude.ai 的聊天介面一樣。

這個 relay 不是 network tunnel。它不轉發 TCP packet。它轉發的是結構化的 application message - 聊天 prompt、tool 執行結果、狀態更新。跟 ngrok 或 VS Code Remote Tunnels 那種轉發 raw network traffic 的完全不同。

這也代表 remote control 沒辦法暴露任意的 port 或 service。它被限制在 Claude Code 的 conversation model 裡面。這不是限制 - 這是比通用 tunnel 小得多的攻擊面。


Session 生命週期

Session Lifecycle

大部分人都是在已經跑著的 session 裡面開啟 remote control:

# 在跑著的 session 裡開啟 remote control
/remote-control
 
# 簡寫
/rc
 
# 或是直接從 CLI 開一個全新的 session
claude remote-control

第一步:註冊

CLI 送一個 HTTPS POST 到 Anthropic API 去註冊 session。API 回傳:

  • 一個 session ID — UUID 格式
  • 一個 session URL — 在 claude.ai/code 底下,指向這個特定 session
  • 多組短期 credential — 每組只對應單一用途

第二步:QR Code

終端機顯示:

  • 一個可以點的 session URL
  • 一個 QR code(用空白鍵切換顯示)

沒有 pairing protocol。沒有 Bluetooth handshake。沒有 device attestation。掃一下就連上了。這種簡潔性是這個設計最好也最糟糕的地方 - 安全性那段會再提到。

第三步:Poll Loop

CLI 進入一個迴圈:

while session_alive:
    response = HTTPS_GET("/sessions/{id}/poll", session_token)
    if response.has_new_message:
        execute_locally(response.message)
        stream_results_back()
    wait(poll_interval)   # 大約 2-5 秒

確切的 polling 間隔沒有文件記載。根據實際使用的體感 - remote 指令幾乎是即時到達,偶爾有一點點延遲 - 我猜大概 2-5 秒。可能是 adaptive 的:對話活躍時縮短,閒置時拉長。

第四步:手機連上

掃完 QR code 之後:

  1. Claude app 打開 session URL
  2. Anthropic 確認你是 Max plan 帳號
  3. Session 出現在你的 session 列表裡,帶一個綠色圓點
  4. 完整的對話紀錄同步到你的手機

從這裡開始就是雙向的了。在手機上打字 → relay → CLI 在本機執行 → 結果透過 relay 串流回來 → 手機渲染。從 terminal 打也是同一個流程。兩邊保持同步。


Heartbeat 問題

這裡開始有趣了 - 也是目前實作露出破綻的地方。

10 分鐘硬斷線

如果你的機器斷網大約 10 分鐘,session 就死了。CLI process 直接結束。你得重新跑 /rc

這代表 server 端有一個 session TTL。Relay 對每個 session 維護一個計時器。每次成功 poll 就重置。超過 10 分鐘沒 poll 到,relay 就宣告 session 死亡然後清理掉。

休眠能活

筆電蓋起來,session 不會死 - 只要休眠時間沒超過 timeout。機器醒來後,CLI 繼續 polling,計時器重置,你又回來了。不需要什麼特殊的 sleep detection 邏輯。Poll loop 自然就能處理。

手機根本不知道你斷線了

問題來了。CLI 離線的時候,手機完全不知道

來自 GitHub issue #28571

"When the connection drops, there is no indication on the iOS app that the connection is lost. The session still appears 'Interactive' on iOS even after disconnection. Messages silently fail."

轉圈圈的動畫繼續轉。UI 看起來一切正常。你打了一則訊息,看起來像是送出去了,但它就這樣消失了。

這告訴我們 heartbeat 是單向的。CLI poll relay(證明自己還活著),但 relay 不會主動推送健康狀態給 remote client。手機分不出「server 掛了」跟「只是還沒收到回應」的差別。

教科書級的分散式系統問題。

如果是我來修

如果讓我來設計的話:

  1. Server 端:relay 對每個 session 發布一個 last_seen timestamp,每次 CLI 成功 poll 就更新
  2. Client 端:手機訂閱 last_seen。如果 now - last_seen > 15s,顯示黃色「連線可能不穩定」的警告。超過 60 秒,顯示紅色「連線已中斷」
  3. Optimistic delivery:斷線期間打的訊息在 client 端排隊,顯示「待送出」的 badge。CLI 回來時送出去。超過 10 分鐘就 timeout 顯示「送出失敗」

跟 WhatsApp 的送達狀態是同一個 pattern - 一個勾勾代表送到 server,兩個勾勾代表送到裝置,藍色代表已讀。


重連機制

網路斷了,CLI 不會馬上放棄。

已知行為

  • 機器恢復上線後 session 會自動重連
  • 持續斷線超過大約 10 分鐘,session 就 timeout 了
  • Timeout 之後要重跑 /rc。舊的對話可以用 --resume 存取,但 remote 連結就沒了

Backoff 策略

幾乎可以確定是 exponential backoff - 這是 HTTP polling retry 的業界標準,而且觀察到的行為也吻合:

retry_interval = min(1s * 2^attempt, 30s)
// 1s, 2s, 4s, 8s, 16s, 30s, 30s, 30s...
// 大約 20 次 retry 就到 10 分鐘 timeout

手機端重連是壞的

CLI 重連沒問題。手機不行。來自 GitHub issue #28402

"Navigating away from the session on mobile loses the connection permanently. The original session URL doesn't reconnect — it opens a new unlinked thread."

強制關閉 app 再重開 - 你會看到幾小時前的舊對話狀態。唯一的選項是「新 session」,這樣就把所有 context 都丟掉了。

這是一個 client 端的 state management bug。App 顯然沒有在本機持久化 session binding,所以 restart 之後就找不到回 relay session 的路了。


安全模型

Security Model

四層防禦。三層很穩。一層出奇地弱。

第一層:傳輸層

TLS 加密。只有往外的 HTTPS 連到 api.anthropic.com - 跟一般 Claude API call 用的是同一個 domain。這代表:

  • 不需要特殊的防火牆規則
  • 流量跟正常 API 用量混在一起(好壞參半)
  • 企業 proxy 如果白名單了 api.anthropic.com,remote control 自動就能通過

第二層:身份驗證

CLI 端透過 claude /login(OAuth)驗證。手機端需要 claude.ai Max plan 的登入。兩個獨立的檢查。

第三層:Scoped Credential

多組短期 token,每組只做一件事:

  • session_token — 識別 session
  • relay_token — 授權 message relay
  • auth_token — 驗證身份

每個獨立過期。一組被洩漏不會連帶其他組一起炸。

第四層:Session URL — 最弱的一環

AgentSteer 的安全分析發現:

"The session URL itself functions as a master authentication token... the 'skeleton key' granting full access regardless of credential rotation policies."

拿到 URL,就能操作 session。攻擊路徑:

  • QR code 偷看 — 咖啡廳裡有人偷拍一張
  • 截圖外洩 — 你截了 QR code 的圖要傳給自己,結果同步到 iCloud 照片串流
  • 瀏覽器歷史紀錄 — URL 就躺在你的瀏覽紀錄裡
  • Slack 貼上 — 你把 URL 丟給同事「測試一下」
  • 螢幕錄影 — pair programming 的時候有人錄到了

C2 的影子

AgentSteer 還指出一個結構性的疑慮:

persistent outbound connection → legitimate domain → auto-reconnect → arbitrary shell execution

如果攻擊者拿到 session URL,他們就有了一個類似 C2 的通道:走合法的 anthropic.com HTTPS,能穿過防火牆,能跑 bash,能存取 SSH key 跟 .env 檔案,而且斷線會自動重連。企業資安團隊應該注意這個。

Sandbox

# 啟用 sandbox(限制檔案系統 + 網路存取)
claude remote-control --sandbox
 
# 預設:不開 sandbox
claude remote-control

Sandbox 預設是關的。開啟後會限制檔案系統存取只能在專案目錄內,也限制網路存取。大部分人根本不知道要開。而且如果你是在 session 裡面用 /rc 開啟 remote control,--sandbox flag 根本就不能用。


狀態同步

當你的手機加入一個已經在跑的 session,它需要完整的對話歷史。如果 agent 正在跑 tool call,partial output 還在串流中,這件事就不簡單了。

根據 Agent SDK 的 session management 運作方式(它支援 --resume 並完整重建歷史),同步大概是這樣:

  1. 手機連上 relay
  2. Relay 送出到目前為止累積的完整對話歷史
  3. 如果 agent 正在執行中,streaming event 會繼續流向新連上的 client
  4. CLI 持有權威狀態;remote UI 只是它的一個 view

這是一個 append-only log。對話就是一串事件 - user message、assistant message、tool call、tool result。Relay 存這個 log。新 client 連上時拿到完整 log,然後訂閱新事件。

已知的同步問題:

  • 重連後出現過時狀態(顯示幾小時前的對話)
  • 沒有 incremental resync - 斷線期間漏掉的事件,沒有「給我 sequence N 之後的所有事件」的機制
  • Client 端狀態可能悄悄跟 relay 的狀態產生偏移

正確的修法是每個事件都加上 sequence number。Client 追蹤「我已經看到 #47」,重連時要求「#47 之後的所有東西」。Slack 跟 Discord 就是這樣做的。


延遲

一個 remote 指令的跳數:

手機 → Anthropic relay → CLI(本機)
~50ms      ~10ms          ~0ms
              ↓
    CLI 執行 tool(例如 git diff)
              ~200ms
              ↓
    CLI → Anthropic relay → 手機
    ~10ms      ~50ms

一個簡單 tool call 的完整 round-trip:大約 320ms。LLM 推論再加上 1-30 秒,那才是你真正在等的東西。

Relay 多跳加的大概是 60-100ms。對一個聊天介面來說 - 使用者打完 prompt 然後等好幾秒 AI 回應 - 這根本感覺不到。系統天生就是 latency-tolerant 的 - 它不是 remote desktop,也不是遊戲 server。


跟類似系統的比較

Claude Code RCVS Code TunnelsTailscale DERPngrok
Relay 的東西App messageNetwork trafficNetwork packetTCP stream
驗證Session URL + 帳號GitHub/MS 帳號WireGuard keyAuth token
加密TLS(號稱 E2E)TLSWireGuard(真 E2E)TLS
重連自動 < 10 分鐘自動自動 + 直連升級可設定
開源部分是(DERP server)
攻擊面只有 chat + tool完整 network完整 network完整 network

Claude Code 的攻擊面比 tunnel-based 的方案小(只有結構化 message),但驗證模型比 Tailscale(WireGuard key exchange)或 VS Code(GitHub 帳號 + device binding)弱。


如果我來改的話

如果讓我設計 Remote Control v2:

Device binding — 把 session URL 綁到 device fingerprint。掃 QR code 的時候觸發 challenge-response,包含手機的 device attestation(Apple DeviceCheck / Android SafetyNet)。洩漏的 URL 在另一台裝置上就沒用了。

雙向 heartbeat — relay 主動推送連線健康狀態給所有 client:

{"type": "heartbeat", "cli_last_seen": "2026-02-26T10:00:05Z", "latency_ms": 47}

Event sequence number — 每個事件都給一個遞增的 sequence number。Client 追蹤自己的位置。重連時從斷點繼續。徹底消除 app restart 後的過時狀態問題。

Sandbox 預設開啟 — 把預設值反過來。claude remote-control 預設就開 sandbox。需要完整存取權限的人用 --no-sandbox 自己開。

Session TTL — 可設定的 session 存活時間。claude remote-control --ttl 2h 代表 session 在 2 小時後自動過期,不管連線狀態如何。


自己試試看

# 最常見的用法:在跑著的 session 裡開啟
/rc
 
# 或從 CLI 開一個全新 session
claude remote-control
 
# 帶 sandbox(第一次試建議開)
claude remote-control --sandbox
 
# 帶 verbose logging(看協定細節)
claude remote-control --verbose

用手機掃 QR code,打點東西,看你的 terminal 在本機執行它。

可以測試的東西:

  • 把筆電的 wifi 關 30 秒,再打開。Session 還活著嗎?
  • 關 wifi 11 分鐘。
  • 在手機上強制關閉 Claude app 再重開。對話還在嗎?(大概不會。)
  • 在兩個瀏覽器分頁同時打開 session URL。
  • 把 session URL 傳給一個有 Max plan 的朋友。他能連上嗎?

真正的工程決策就藏在這些地方。


總結

Remote Control 是一個 relay-based、outbound-only 的 messaging bridge,連接你本機的 CLI 跟遠端的 UI。不是 network tunnel。這一個設計決策影響了所有東西:安全模型、延遲特性、攻擊面、各種限制。

v1 做得很紮實。掃個 QR code 就進入一個能用的 session,是真的很厲害。但工程上的接縫看得到:單向 heartbeat、缺少 sequence number、sandbox 預設不開、session URL 當萬能鑰匙。全部都修得了。

如果你在做 agent infrastructure 的事 - 不是單純用,而是在造 - 仔細研究這個設計。Relay pattern、scoped credential、application-level 的 message forwarding:這些就是 production agent 系統的基本建構元件。而那些 failure mode - 過時狀態、無聲斷線、URL-as-bearer-token - 就是你如果沒提早想到,自己的系統也會踩到的 bug。

Comments