세션 관리

추가 전용 JSONL 트랜스크립트, parentUuid 연결 리스트, 세션 상태 머신, 대화 복구.

01

개요

Claude Code의 세션 관리 시스템은 대화 히스토리를 추가 전용(append-only) JSONL 파일로 지속하고, parentUuid 기반 연결 리스트로 메시지 체인을 관리하며, 상태 머신으로 세션 수명 주기를 추적합니다. 이 시스템은 중단된 세션의 복구, 효율적인 세션 목록 표시, 클라우드 동기화까지 지원합니다.

다루는 소스 파일: session/ 디렉토리 — sessionManager.ts, sessionStore.ts, sessionState.ts, sessionResume.ts, projectSingleton.ts
02

디스크 레이아웃

세션 파일은 ~/.claude/projects/ 디렉토리 아래에 프로젝트 경로를 반영한 구조로 저장됩니다:

# 디스크 레이아웃 예시
~/.claude/projects/
  └── home-user-myproject/          # 프로젝트 경로 인코딩
      ├── sessions/
      │   ├── abc123.jsonl          # 세션 트랜스크립트 (추가 전용)
      │   └── def456.jsonl
      ├── memory.md                 # 세션 메모리
      └── config.json               # 프로젝트 설정

Project 싱글톤과 쓰기 큐

각 프로젝트는 싱글톤 인스턴스로 관리됩니다. 세션 파일에 대한 쓰기는 100ms 배치 큐를 통해 수행되어 디스크 I/O를 최소화합니다. 짧은 시간 내에 발생하는 여러 메시지 추가가 단일 쓰기 작업으로 통합됩니다.

// projectSingleton.ts — 100ms 배치 쓰기
class WriteQueue {
  private pending: Entry[] = []
  private timer: NodeJS.Timeout | null = null

  enqueue(entry: Entry): void {
    this.pending.push(entry)
    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), 100)
    }
  }
}
03

JSONL 엔트리 타입 (16종)

세션 JSONL 파일에는 16종의 엔트리 타입이 기록됩니다. 각 엔트리는 type, uuid, parentUuid, timestamp 필드를 공통으로 가집니다:

딥 다이브 — isMeta 메시지

isMeta 플래그가 설정된 메시지는 UI에 표시되지 않는 내부 제어 메시지입니다. 예를 들어 인터럽트 후 복구 시 추가되는 "Continue from where you left off." 메시지가 isMeta: true로 표시됩니다. 이 메시지는 모델에게 지시를 전달하지만 사용자에게는 보이지 않습니다.

04

parentUuid 연결 리스트

JSONL 파일의 엔트리는 parentUuid 필드를 통해 연결 리스트를 형성합니다. 이 구조는 대화 브랜칭, 압축 후 재구성, 부분 로드를 가능하게 합니다.

체인 워킹

체인 구축은 리프(LEAF) 메시지에서 시작하여 parentUuid를 따라 역방향으로 워킹한 후, 최종적으로 reverse()를 호출하여 시간순으로 정렬합니다:

// sessionStore.ts — 역방향 체인 워킹
function buildChain(leafUuid: string, entries: Map): Entry[] {
  const chain: Entry[] = []
  let current = entries.get(leafUuid)

  while (current) {
    chain.push(current)
    current = entries.get(current.parentUuid)
  }

  return chain.reverse() // 시간순 정렬
}

사이클 감지

체인 워킹 중 방문한 UUID를 Set으로 추적하여 순환 참조를 감지합니다. 사이클이 감지되면 체인 워킹을 즉시 중단하고 현재까지 수집된 체인을 반환합니다.

05

세션 상태 머신

세션은 세 가지 상태를 가지는 상태 머신으로 관리됩니다:

// sessionState.ts — 상태 전이
idle → running          // 사용자 메시지 전송
running → idle          // 모델 응답 완료
running → requires_action  // 권한 프롬프트 필요
requires_action → running  // 사용자가 허용/거부
requires_action → idle     // 사용자가 중단
06

대화 복구

인터럽트 감지

세션 재개(resume) 시 마지막 메시지가 assistant 타입이고 stop_reason이 없으면, 이전 세션이 비정상 종료(Ctrl+C, 크래시 등)된 것으로 판단합니다. 이 경우 "Continue from where you left off." isMeta 메시지가 자동으로 추가됩니다.

loadConversationForResume — 4개 소스

세션 복구 시 대화 내용을 4개 소스에서 로드합니다:

  1. 로컬 JSONL 파일 — 기본 소스
  2. 클라우드 저장소 — 로컬 파일이 없을 때 폴백
  3. 메모리 캐시 — 최근 세션의 인메모리 캐시
  4. 압축된 요약 — 전체 로드가 불가능할 때 압축 요약 사용

클라우드 지속성 (이중 원격 경로)

세션 데이터는 두 개의 원격 경로로 동기화됩니다. 이중 경로는 마이그레이션 기간 동안 이전 경로와 새 경로 모두에서 읽기를 보장합니다.

꼬리 윈도우 메타데이터 (64KB)

세션 목록을 표시할 때 전체 JSONL 파일을 읽지 않고, EOF에서 64KB 꼬리 윈도우만 읽습니다. 메타데이터(session_start, 마지막 메시지 요약 등)는 압축이나 업데이트 시 EOF에 재추가되어 항상 꼬리 윈도우 내에 존재하도록 보장합니다.

// sessionStore.ts — 64KB 꼬리 읽기
function readSessionMetadata(path: string): Metadata {
  const stat = fs.statSync(path)
  const tailOffset = Math.max(0, stat.size - 65536) // 64KB
  const tail = fs.readSync(fd, buffer, tailOffset, 65536)
  return parseMetadataFromTail(tail)
}
딥 다이브 — 세션 파일 생성 시점

세션 파일은 claude 시작 시가 아니라 첫 실제 user 또는 assistant 메시지가 발생할 때 생성됩니다. 세션 ID는 부트 시 할당되지만, 사용자가 프롬프트를 입력하지 않고 종료하면 빈 세션 파일이 남지 않습니다. 이는 세션 목록의 노이즈를 줄이고 디스크 공간을 절약합니다.

07

핵심 요약

핵심 포인트

  • 세션 데이터는 ~/.claude/projects/ 아래 추가 전용 JSONL 파일로 지속됩니다
  • Project 싱글톤의 100ms 배치 큐가 디스크 I/O를 최소화합니다
  • 16종의 엔트리 타입이 메시지, 도구, 메타데이터, 상태를 포괄합니다
  • parentUuid 체인은 LEAF에서 역방향 워킹 후 reverse()로 시간순 정렬합니다
  • 세션 상태 머신은 idle, running, requires_action 세 가지 상태를 가집니다
  • 인터럽트 감지 시 "Continue from where you left off." isMeta 메시지가 자동 추가됩니다
  • 세션 파일은 첫 실제 메시지 시 생성되어 빈 세션 파일을 방지합니다
  • 64KB 꼬리 윈도우 메타데이터가 EOF에 재추가되어 라이트 읽기로 세션 목록을 표시합니다
08

지식 확인

퀴즈 — 5문제

Q1. 세션 파일이 실제 생성되는 시점은?

  • A) claude 시작 시
  • B) 첫 실제 user/assistant 메시지 시
  • C) 첫 도구 호출 시
  • D) 세션 ID 할당 시
세션 ID는 부트 시 할당되지만, JSONL 파일은 첫 실제 메시지가 발생할 때 생성됩니다. 프롬프트 없이 종료하면 파일이 생성되지 않아 불필요한 빈 세션을 방지합니다.

Q2. parentUuid 연결 리스트의 체인 구축 방법은?

  • A) 처음부터 순방향으로 탐색
  • B) LEAF에서 시작하여 parentUuid를 따라 역방향 워킹 후 reverse()
  • C) 이진 검색으로 최적 경로 탐색
  • D) 인덱스 테이블에서 직접 조회
체인은 리프(최신 메시지)에서 시작하여 parentUuid 링크를 따라 루트까지 역방향으로 워킹합니다. 수집된 엔트리를 reverse()하여 시간순으로 정렬합니다. 이 방식은 JSONL의 추가 전용 특성과 자연스럽게 맞습니다.

Q3. interrupted_turn 감지 시 수행하는 동작은?

  • A) 오류 메시지 표시
  • B) 세션 종료
  • C) "Continue from where you left off." isMeta 메시지 추가
  • D) 마지막 메시지 삭제
인터럽트가 감지되면 isMeta: true 플래그가 설정된 "Continue from where you left off." 메시지가 추가됩니다. 이 메시지는 모델에게 이전 작업을 계속하라는 지시를 전달하지만, 사용자 UI에는 표시되지 않습니다.

Q4. 꼬리 윈도우 메타데이터가 EOF에 재추가되는 이유는?

  • A) 성능 최적화
  • B) 재개 선택기가 64KB 꼬리만 읽으므로, 메타데이터가 윈도우 내에 있어야 함
  • C) 정렬 순서 유지
  • D) 중복 방지
세션 목록(재개 선택기)은 전체 JSONL을 읽지 않고 EOF에서 64KB만 읽습니다. 압축이나 메타데이터 업데이트 시 session_start 등의 메타데이터가 파일 끝에 재추가되어, 항상 꼬리 윈도우 내에서 접근 가능하도록 보장합니다.

Q5. 세션 상태 머신의 3가지 상태는?

  • A) start / run / stop
  • B) active / paused / error
  • C) idle / running / requires_action
  • D) init / query / complete
세션 상태 머신은 idle(입력 대기), running(응답 생성/도구 실행 중), requires_action(사용자 액션 필요) 세 가지 상태를 가집니다. 이 세 상태 사이의 전이가 세션의 전체 수명 주기를 관리합니다.
0 / 5