# Claude Code Session Storage - Technical Documentation

This document contains detailed information about how Claude Code stores and manages sessions, discovered through exploration of the actual file system and session files.

## Session Storage Location

Sessions are stored locally in JSONL (JSON Lines) format:

```
~/.claude/projects/<encoded-project-path>/<session-uuid>.jsonl
```

### Example Structure
```
~/.claude/
├── .claude.json              # Global state and config
├── projects/
│   └── -Users-dberges-Desktop-timbal-ai-timbal/
│       ├── 24c283de-5a27-4aa2-ae89-a0185440764a.jsonl
│       ├── 24c283de-5a27-4aa2-ae89-a0185440764a/
│       │   └── subagents/
│       │       └── agent-a4084e7.jsonl
│       ├── 3e23f59b-d09b-4133-84a7-27dd700460d7.jsonl
│       └── ...
├── history.jsonl             # Command history
├── cache/                    # Cache directory
├── debug/                    # Debug logs
├── plans/                    # Plan mode files
├── todos/                    # Todo lists
└── ...
```

## Session File Format

Each session is stored as a JSONL file where each line is a JSON object representing an event.

### Event Types
- `user` - User messages
- `assistant` - Assistant responses
- `system` - System messages
- `file-history-snapshot` - File state tracking

### Message Structure

```json
{
  "type": "user",
  "uuid": "message-uuid",
  "sessionId": "session-uuid",
  "timestamp": "2026-01-15T22:28:48.109Z",
  "cwd": "/path/to/project",
  "gitBranch": "main",
  "message": {
    "role": "user",
    "content": "your message text here"
  },
  "version": "1.0.0",
  "parentUuid": "previous-message-uuid",
  "requestId": "request-uuid",
  "slug": "session-name-if-renamed",
  "userType": "human",
  "isSidechain": false
}
```

### Assistant Message Structure

```json
{
  "type": "assistant",
  "uuid": "message-uuid",
  "sessionId": "session-uuid",
  "timestamp": "2026-01-15T22:28:50.123Z",
  "cwd": "/path/to/project",
  "gitBranch": "main",
  "message": {
    "id": "msg_xyz",
    "type": "message",
    "role": "assistant",
    "model": "claude-sonnet-4-5-20250929",
    "content": [
      {
        "type": "text",
        "text": "Response text here"
      },
      {
        "type": "tool_use",
        "id": "toolu_123",
        "name": "Read",
        "input": {}
      }
    ],
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 100,
      "output_tokens": 50,
      "cache_creation_input_tokens": 0,
      "cache_read_input_tokens": 0
    }
  },
  "version": "1.0.0"
}
```

### File History Snapshot

```json
{
  "type": "file-history-snapshot",
  "messageId": "message-uuid",
  "snapshot": {
    "messageId": "message-uuid",
    "trackedFileBackups": {},
    "timestamp": "2026-01-15T22:28:17.612Z"
  },
  "isSnapshotUpdate": false
}
```

## ~/.claude.json - Global State File

The `~/.claude.json` file contains global configuration and state.

### Key Structure

```json
{
  "projects": {
    "/path/to/project": {
      "lastSessionId": "24c283de-5a27-4aa2-ae89-a0185440764a",
      "allowedTools": [],
      "mcpServers": {},
      "lastCost": 0.124,
      "lastTotalInputTokens": 201,
      "lastTotalOutputTokens": 2929,
      "lastModelUsage": {
        "claude-sonnet-4-5-20250929": {
          "inputTokens": 42,
          "outputTokens": 1625,
          "costUSD": 0.066
        }
      },
      "exampleFiles": ["file1.py", "file2.py"],
      "hasCompletedProjectOnboarding": true
    }
  },
  "oauthAccount": {...},
  "userID": "...",
  "hasCompletedOnboarding": true
}
```

### Important Finding: No Session Registry

**The `~/.claude.json` file ONLY stores `lastSessionId` per project - it does NOT contain a list of all sessions.**

Session discovery works by:
1. Scanning `~/.claude/projects/` directory structure
2. Reading `.jsonl` files to extract metadata
3. Grouping and displaying in the session picker

This means you can add session files directly to the filesystem without updating `~/.claude.json`.

## Session Discovery Mechanism

When you run `claude --resume`:

1. Scans `~/.claude/projects/` for all `.jsonl` files
2. Parses first few lines to extract:
   - Session ID
   - Timestamp
   - Project path
   - Git branch
   - Session name (slug)
3. Groups sessions by project
4. Displays in interactive picker

## Manual Session Creation

You can manually create sessions by creating properly formatted `.jsonl` files.

### Basic Example

```bash
#!/bin/bash

# Generate UUIDs
SESSION_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
MESSAGE_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
PROJECT_PATH="/workspace"
ENCODED_PATH="-workspace"  # Replace / with -

# Create project directory
mkdir -p ~/.claude/projects/${ENCODED_PATH}

# Create minimal session file
cat > ~/.claude/projects/${ENCODED_PATH}/${SESSION_ID}.jsonl <<EOF
{"type":"user","uuid":"${MESSAGE_ID}","sessionId":"${SESSION_ID}","timestamp":"$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")","cwd":"${PROJECT_PATH}","gitBranch":"main","message":{"role":"user","content":"Initial prompt here"},"version":"1.0.0"}
EOF

echo "Session created: ${SESSION_ID}"
```

### Multi-Turn Conversation Example

```bash
#!/bin/bash

SESSION_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
PROJECT_PATH="/workspace"
ENCODED_PATH="-workspace"

mkdir -p ~/.claude/projects/${ENCODED_PATH}

cat > ~/.claude/projects/${ENCODED_PATH}/${SESSION_ID}.jsonl <<EOF
{"type":"user","uuid":"$(uuidgen | tr '[:upper:]' '[:lower:]')","sessionId":"${SESSION_ID}","timestamp":"2026-01-15T10:00:00.000Z","cwd":"${PROJECT_PATH}","gitBranch":"main","message":{"role":"user","content":"First question"},"version":"1.0.0"}
{"type":"assistant","uuid":"$(uuidgen | tr '[:upper:]' '[:lower:]')","sessionId":"${SESSION_ID}","timestamp":"2026-01-15T10:00:05.000Z","cwd":"${PROJECT_PATH}","gitBranch":"main","message":{"role":"assistant","content":"First response"},"version":"1.0.0"}
{"type":"user","uuid":"$(uuidgen | tr '[:upper:]' '[:lower:]')","sessionId":"${SESSION_ID}","timestamp":"2026-01-15T10:01:00.000Z","cwd":"${PROJECT_PATH}","gitBranch":"main","message":{"role":"user","content":"Follow-up question"},"version":"1.0.0"}
EOF
```

## Docker Portability Strategies

### Strategy 1: Volume Mount (Recommended)

Mount the entire Claude config directory:

```dockerfile
# Dockerfile
FROM your-base-image

# Set custom config location (optional)
ENV CLAUDE_CONFIG_DIR=/data/claude

# Application code
WORKDIR /workspace
COPY . .
```

```bash
# Run with volume
docker run -v claude-data:/data/claude \
           -v $(pwd):/workspace \
           your-image

# Sessions persist across container restarts
```

### Strategy 2: Pre-populate Sessions at Build Time

```dockerfile
# Dockerfile
FROM your-base-image

# Copy pre-created session files
COPY ./docker/claude-sessions/*.jsonl /root/.claude/projects/-workspace/

# Or copy entire Claude config
COPY ./docker/claude-config /root/.claude

WORKDIR /workspace
```

### Strategy 3: Session Injection at Runtime

```bash
# inject-sessions.sh
#!/bin/bash

# Copy sessions from host to container
docker cp ./sessions/*.jsonl container-id:/root/.claude/projects/-workspace/

# Or use a mounted volume
docker run -v ./session-templates:/tmp/sessions \
           your-image bash -c "cp /tmp/sessions/*.jsonl ~/.claude/projects/-workspace/"
```

### Strategy 4: Backup and Restore

```bash
# Backup all sessions
tar -czf claude-sessions-backup.tar.gz ~/.claude/

# Restore to new container
docker cp claude-sessions-backup.tar.gz container-id:/tmp/
docker exec container-id tar -xzf /tmp/claude-sessions-backup.tar.gz -C /root/

# Or use volumes for automatic backup
docker run -v claude-backup:/root/.claude your-image
```

### Complete Docker Example

```dockerfile
FROM ubuntu:22.04

# Install Claude Code
RUN curl -fsSL https://claude.com/install.sh | sh

# Set custom config directory for easy volume mounting
ENV CLAUDE_CONFIG_DIR=/persistent/claude

# Create directories
RUN mkdir -p /persistent/claude/projects/-workspace

# Copy session templates (optional)
COPY ./session-templates/*.jsonl /persistent/claude/projects/-workspace/

WORKDIR /workspace
```

```bash
# Run with persistent storage
docker run -it \
  -v claude-persistent:/persistent/claude \
  -v $(pwd):/workspace \
  your-claude-image

# Sessions persist across:
# - Container stops/starts
# - Container deletions (if volume is preserved)
# - Container rebuilds
```

## Automatic Cleanup Configuration

Add to `~/.claude/settings.json` or `.claude/settings.json`:

```json
{
  "cleanupPeriodDays": 20
}
```

This will automatically delete sessions inactive for more than 20 days on startup.

## Session File Sizes (Typical)

From actual measurements:
- Empty/minimal sessions: 4 KB
- Active conversation: 60-170 KB
- Total storage for 8 sessions: ~1.1 MB

Sessions are relatively lightweight.

## Important Notes

### Path Encoding
Project paths are encoded by replacing `/` with `-`:
- `/Users/dberges/Desktop/timbal-ai/timbal`
- becomes `-Users-dberges-Desktop-timbal-ai-timbal`

### Subagent Sessions
Subagent sessions are stored in subdirectories:
```
<session-id>/
  subagents/
    agent-<agent-id>.jsonl
```

### Authentication Caveat
OAuth tokens in `~/.claude.json` are environment-specific. When moving sessions between environments (like Docker containers), you may need to re-authenticate.

### Schema Stability
The session file format is an internal implementation detail and may change between Claude Code versions. For production use, prefer official APIs/interfaces over direct file manipulation.

## Practical Recommendations

1. **For local persistence**: Use default storage, configure `cleanupPeriodDays`
2. **For Docker/containers**: Use volume mounts with `CLAUDE_CONFIG_DIR`
3. **For portability**: Export/import entire `~/.claude/` directory
4. **For session templates**: Pre-create `.jsonl` files and copy to target location
5. **For CI/CD**: Mount read-only session templates as volumes

## Additional Files

- `~/.claude/history.jsonl` - Command line history
- `~/.claude/todos/` - Todo list states per session
- `~/.claude/plans/` - Plan mode files
- `~/.claude/debug/` - Debug logs
- `~/.claude/shell-snapshots/` - Background shell states

## Summary

- Sessions are stored as individual `.jsonl` files
- No central session registry - discovery by filesystem scan
- Manual creation is possible with proper JSON structure
- Docker portability achieved through volume mounts
- `~/.claude.json` only tracks `lastSessionId`, not all sessions
- Path encoding: replace `/` with `-`
- Cleanup can be automated with `cleanupPeriodDays` setting
