ClaudeCode.History (ClaudeCode v0.36.3)

View Source

Utilities for reading and parsing Claude Code session history files.

Claude Code stores conversation history in JSONL files at: ~/.claude/projects/<encoded-project-path>/<session-id>.jsonl

This module provides functions to:

  • List sessions with rich metadata (list_sessions/1)
  • Read and parse session JSONL files
  • Extract conversation history with proper chain building (get_messages/2)

Session File Format

Session files contain various message types:

  • user - User messages (prompts and tool results)
  • assistant - Assistant responses
  • system - System events (errors, etc.)
  • summary - Conversation summary
  • file-history-snapshot - File tracking metadata
  • queue-operation - Internal operations

Examples

# List sessions with metadata
{:ok, sessions} = ClaudeCode.History.list_sessions(directory: ".")
Enum.each(sessions, fn s -> IO.puts("#{s.summary} (#{s.session_id})") end)

# Get messages with proper chain building
{:ok, messages} = ClaudeCode.History.get_messages("abc123-def456")

# Read raw session entries
{:ok, entries} = ClaudeCode.History.read_session("abc123-def456")

Summary

Functions

Decodes an encoded project path back to a path format.

Encodes a project path to the format used by Claude Code.

Finds the file path for a session ID.

Reads a session's conversation messages using parentUuid chain building.

Lists all projects that have session history.

Lists sessions with rich metadata extracted from stat + head/tail reads.

Reads a session JSONL file from a specific path and returns all entries as normalized maps.

Reads a session JSONL file by session ID and returns all entries as normalized maps.

Sanitizes a project path matching the Python SDK's _sanitize_path behavior.

Port of the JS simpleHash function (32-bit integer hash, base36).

Gets the conversation summary from a session, if available.

Types

session_id()

@type session_id() :: String.t()

Functions

decode_project_path(encoded)

@spec decode_project_path(String.t()) :: Path.t()

Decodes an encoded project path back to a path format.

Replaces - with /. Note that this encoding is lossy - if the original path contained - or _ characters, they cannot be distinguished from path separators. For example, /a/b-c, /a/b_c, and /a/b/c all encode to -a-b-c.

This function is primarily useful for display purposes. For matching against known paths, use encode_project_path/1 instead.

Examples

iex> ClaudeCode.History.decode_project_path("-Users-me-project")
"/Users/me/project"

encode_project_path(path)

@spec encode_project_path(Path.t()) :: String.t()

Encodes a project path to the format used by Claude Code.

Replaces / and _ with - in the path to match the CLI's encoding.

Examples

iex> ClaudeCode.History.encode_project_path("/Users/me/project")
"-Users-me-project"

iex> ClaudeCode.History.encode_project_path("/Users/me/my_project")
"-Users-me-my-project"

find_session_path(session_id, opts \\ [])

@spec find_session_path(
  session_id(),
  keyword()
) :: {:ok, Path.t()} | {:error, term()}

Finds the file path for a session ID.

Searches through all project directories in ~/.claude/projects/.

Options

  • :project_path - Specific project path to search in (optional)
  • :claude_dir - Override the Claude directory (default: ~/.claude)

Examples

{:ok, "/Users/me/.claude/projects/-my-project/abc123.jsonl"} =
  ClaudeCode.History.find_session_path("abc123")

{:error, {:session_not_found, "abc123"}} =
  ClaudeCode.History.find_session_path("nonexistent")

get_messages(session_id, opts \\ [])

@spec get_messages(
  session_id(),
  keyword()
) :: {:ok, [ClaudeCode.History.SessionMessage.t()]} | {:error, term()}

Reads a session's conversation messages using parentUuid chain building.

Parses the full JSONL, builds the conversation chain via parentUuid links, and returns visible user/assistant messages in chronological order.

Options

  • :project_path - Project directory to find the session in
  • :limit - Maximum number of messages to return
  • :offset - Number of messages to skip from the start (default: 0)
  • :claude_dir - Override ~/.claude (for testing)

Examples

{:ok, messages} = ClaudeCode.History.get_messages("abc123-def456")

# With pagination
{:ok, page} = ClaudeCode.History.get_messages(session_id, limit: 10, offset: 20)

list_projects(opts \\ [])

@spec list_projects(keyword()) :: {:ok, [Path.t()]} | {:error, term()}

Lists all projects that have session history.

Options

  • :claude_dir - Override the Claude directory (default: ~/.claude)

Examples

{:ok, ["/Users/me/project1", "/Users/me/project2"]} =
  ClaudeCode.History.list_projects()

list_sessions(opts \\ [])

@spec list_sessions(keyword()) :: {:ok, [ClaudeCode.History.SessionInfo.t()]}

Lists sessions with rich metadata extracted from stat + head/tail reads.

When :project_path is provided, returns sessions for that project directory (and optionally its git worktrees). When omitted, returns sessions across all projects.

Options

  • :project_path - Project directory to list sessions for (nil = all projects)
  • :limit - Maximum number of sessions to return
  • :include_worktrees - Scan git worktrees (default: true)
  • :claude_dir - Override ~/.claude (for testing)

Examples

# List sessions for the current project
{:ok, sessions} = ClaudeCode.History.list_sessions(project_path: ".")

# List all sessions across all projects
{:ok, sessions} = ClaudeCode.History.list_sessions()

# With limit
{:ok, recent} = ClaudeCode.History.list_sessions(project_path: ".", limit: 10)

read_file(path)

@spec read_file(Path.t()) :: {:ok, [map()]} | {:error, term()}

Reads a session JSONL file from a specific path and returns all entries as normalized maps.

Returns every line as a snake_case string-keyed map, preserving all entry types (user, assistant, system, summary, queue operations, etc.). Keys are normalized from camelCase to snake_case for consistency with live CLI output.

Examples

{:ok, entries} = ClaudeCode.History.read_file("/path/to/session.jsonl")

read_session(session_id, opts \\ [])

@spec read_session(
  session_id(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}

Reads a session JSONL file by session ID and returns all entries as normalized maps.

Searches through all project directories to find the session file. Returns every line as a snake_case string-keyed map, including metadata entries (summaries, queue operations, etc.) that have no SDK struct representation. Use get_messages/2 to get properly chain-built conversation messages.

Options

  • :project_path - Specific project path to search in (optional)
  • :claude_dir - Override the Claude directory (default: ~/.claude)

Examples

{:ok, entries} = ClaudeCode.History.read_session("abc123-def456")

# Search in a specific project
{:ok, entries} = ClaudeCode.History.read_session("abc123", project_path: "/my/project")

sanitize_path(path)

@spec sanitize_path(Path.t()) :: String.t()

Sanitizes a project path matching the Python SDK's _sanitize_path behavior.

Replaces all non-alphanumeric characters with hyphens. For paths exceeding 200 characters, truncates and appends a hash suffix.

Examples

iex> ClaudeCode.History.sanitize_path("/Users/me/project")
"-Users-me-project"

simple_hash(s)

@spec simple_hash(String.t()) :: String.t()

Port of the JS simpleHash function (32-bit integer hash, base36).

summary(session_id, opts \\ [])

@spec summary(
  session_id(),
  keyword()
) :: {:ok, String.t() | nil} | {:error, term()}

Gets the conversation summary from a session, if available.

Returns the summary text or nil if no summary exists.

Examples

{:ok, "User asked about..."} = ClaudeCode.History.summary("abc123-def456")
{:ok, nil} = ClaudeCode.History.summary("new-session-id")