ClOr

ClOr

백엔드 실무 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.

Claude Code 해부학 (완결)

51만 줄 소스코드를 19편에 걸쳐 분석한 완결 시리즈

전체 시리즈 보기 →

백엔드 트러블슈팅

실무에서 겪은 장애와 해결 과정 기록

전체 시리즈 보기 →

최신 글

article thumbnail

결론부터 말하면, 클로드 코드(Claude Code)의 IDE 브릿지는 VS Code나 claude.ai 웹에서 로컬 CLI로 메시지를 전달하는 양방향 통신 계층입니다. 소스코드를 분석해보면 이 브릿지가 단순한 파이프가 아니라, OAuth 인증, 세션 관리, 크래시 복구까지 담당하는 독립 시스템이라는 걸 알 수 있습니다.

목차

  • IDE 브릿지가 뭘 하는 건가요?
  • 브릿지는 어떻게 연결을 시작하나요?
  • 폴링으로 작업을 받는다고요?
  • 메시지는 어떻게 왕복하나요?
  • 에코 메시지는 어떻게 걸러내나요?
  • 크래시가 나면 세션은 어떻게 되나요?
  • 브릿지 접근 권한은 어떻게 관리되나요?
  • 정리
    • Claude Code 해부학 시리즈
    • 관련글

IDE 브릿지가 뭘 하는 건가요?

Claude Code를 터미널에서만 쓸 수 있다면 불편하겠죠. VS Code 안에서, 혹은 claude.ai 웹에서도 로컬 머신의 Claude Code와 대화할 수 있어야 합니다. 이걸 가능하게 하는 게 src/bridge/ 디렉토리의 브릿지 시스템입니다.

핵심 구조는 이렇습니다. 클라이언트(VS Code/웹)가 Anthropic 서버에 메시지를 보내면, 서버가 로컬 Claude Code의 폴링 루프로 작업을 배포합니다. 로컬에서 처리한 결과는 다시 서버를 거쳐 클라이언트에 돌아갑니다.

 

브릿지는 어떻게 연결을 시작하나요?

연결 시작점은 bridgeMain.ts입니다. 이 파일이 환경 등록부터 세션 스폰까지 전체 라이프사이클을 관리합니다.

// src/bridge/types.ts
export type BridgeConfig = {
  dir: string
  machineName: string
  branch: string
  gitRepoUrl: string | null
  maxSessions: number
  spawnMode: SpawnMode
  bridgeId: string
  workerType: string
  environmentId: string
  apiBaseUrl: string
  sessionIngressUrl: string
}

BridgeConfig에 현재 디렉토리, 브랜치, 최대 세션 수 같은 정보가 담깁니다. 이걸 /v1/environments/bridge API에 POST로 보내서 환경을 등록하면, 서버가 environment_idenvironment_secret을 발급해줍니다.

폴링으로 작업을 받는다고요?

네, WebSocket 푸시가 아니라 폴링입니다. bridgeApi.tspollForWork가 서버를 주기적으로 호출합니다.

// src/bridge/bridgeApi.ts
async pollForWork(
  environmentId: string,
  environmentSecret: string,
  signal?: AbortSignal,
): Promise<WorkResponse | null> {
  const response = await axios.get<WorkResponse | null>(
    `${deps.baseUrl}/v1/environments/${environmentId}/work/poll`,
    {
      headers: getHeaders(environmentSecret),
      timeout: 10_000,
      signal,
    },
  )
  return response.data
}

빈 응답이 오면 작업이 없는 거고, WorkResponse가 오면 세션을 스폰합니다. 100번 연속 빈 폴링이면 로그 한 줄만 남기는 정도로 조용하게 처리합니다.

메시지는 어떻게 왕복하나요?

트랜스포트 계층이 v1과 v2 두 버전으로 나뉩니다. replBridgeTransport.ts에 정의된 추상화가 이 차이를 감춥니다.

// src/bridge/replBridgeTransport.ts
export type ReplBridgeTransport = {
  write(message: StdoutMessage): Promise<void>
  writeBatch(messages: StdoutMessage[]): Promise<void>
  close(): void
  isConnectedStatus(): boolean
  setOnData(callback: (data: string) => void): void
  setOnClose(callback: (closeCode?: number) => void): void
  setOnConnect(callback: () => void): void
  connect(): void
  getLastSequenceNum(): number
  flush(): Promise<void>
}

v1은 HybridTransport로 WebSocket 읽기 + HTTP POST 쓰기를 씁니다. v2는 SSE 읽기 + CCRClient HTTP POST 쓰기로 바뀌었습니다. 어떤 버전이든 ReplBridgeTransport 인터페이스 뒤에 숨어 있어서, 호출하는 쪽은 차이를 모릅니다.

에코 메시지는 어떻게 걸러내나요?

bridgeMessaging.tsBoundedUUIDSet이라는 재밌는 자료구조가 있습니다. 링 버퍼 기반의 FIFO 집합입니다.

// src/bridge/bridgeMessaging.ts
export class BoundedUUIDSet {
  private readonly ring: (string | undefined)[]
  private readonly set = new Set<string>()
  private writeIdx = 0

  add(uuid: string): void {
    if (this.set.has(uuid)) return
    const evicted = this.ring[this.writeIdx]
    if (evicted !== undefined) {
      this.set.delete(evicted)
    }
    this.ring[this.writeIdx] = uuid
    this.set.add(uuid)
    this.writeIdx = (this.writeIdx + 1) % this.capacity
  }
}

내가 보낸 메시지가 서버를 거쳐 다시 돌아오는 "에코"를 걸러내는 용도입니다. 용량이 고정된 링 버퍼라서 메모리가 무한히 늘어나지 않습니다. 가장 오래된 UUID가 자동으로 밀려나는 구조죠.

크래시가 나면 세션은 어떻게 되나요?

bridgePointer.ts가 크래시 복구를 담당합니다. 세션이 생성되면 즉시 포인터 파일을 쓰고, 주기적으로 mtime을 갱신합니다. 프로세스가 비정상 종료되어도 이 파일이 남아있죠.

// src/bridge/bridgePointer.ts
export const BRIDGE_POINTER_TTL_MS = 4 * 60 * 60 * 1000

export async function writeBridgePointer(
  dir: string,
  pointer: BridgePointer,
): Promise<void> {
  const path = getBridgePointerPath(dir)
  await mkdir(dirname(path), { recursive: true })
  await writeFile(path, jsonStringify(pointer), 'utf8')
}

TTL은 4시간입니다. 다음 claude remote-control 실행 시 이 포인터가 감지되면 --session-id 플로우로 기존 세션을 이어받습니다. 4시간이 지났으면 서버도 이미 환경을 정리했을 테니 포인터를 삭제합니다.

브릿지 접근 권한은 어떻게 관리되나요?

bridgeEnabled.ts에서 다단계 검증을 합니다. claude.ai 구독자인지, OAuth 토큰에 프로필 스코프가 있는지, 조직 UUID가 있는지, GrowthBook 피처 플래그가 켜져 있는지까지 순서대로 확인합니다.

하나라도 실패하면 구체적인 안내 메시지를 돌려줍니다. "구독이 필요합니다", "풀 스코프 로그인이 필요합니다" 같은 식으로요. 사용자가 뭘 해야 하는지 명확하게 알려주는 좋은 UX 설계입니다.

정리

  • IDE 브릿지는 VS Code/웹과 로컬 CLI 사이의 양방향 통신 계층이다
  • 폴링 방식으로 서버에서 작업을 받아오며, v1(WebSocket)과 v2(SSE+CCR) 두 트랜스포트를 지원한다
  • BoundedUUIDSet 링 버퍼로 에코 메시지를 메모리 고정 비용으로 제거한다
  • bridgePointer로 크래시 후 4시간 이내에 세션을 자동 복구할 수 있다
  • 다단계 권한 검증으로 구독/토큰/피처플래그를 모두 확인해야 접속된다

관련글

profile

ClOr

@ClOr

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

ClOr · 백엔드 트러블슈팅과 AI 에이전트 구조 분석을 기록합니다.