ClaudeWrapper.Jobs (ClaudeWrapper v0.8.0)

Copy Markdown View Source

Read-side access to Claude Code's on-disk background-job state.

Claude Code ships a supervisor daemon (claude daemon run) that orchestrates background agent tasks launched via the claude agents TUI. Per-task state lives under ~/.claude/jobs/<short_id>/:

  • state.json -- current snapshot: state, intent (original prompt), session id, link to the session JSONL, timestamps, auto-generated name, etc.
  • timeline.jsonl -- append-only event log: at each state transition the daemon writes a line carrying timestamp, new state, a one-line detail, and (often) the full text body.

The session content itself is a normal ~/.claude/projects/<slug>/<session_id>.jsonl -- the same format ClaudeWrapper.History parses. Each summary's session_path points at it for direct cross-linking.

This module is read-only on purpose. The dispatch protocol (how the TUI launches new tasks) is undocumented and version-sensitive; mirroring it would defeat the drift defenses the wrapper relies on.

Liberal parsing

Like ClaudeWrapper.History, parsing is forgiving. list/1 skips entries that are not directories (e.g. the daemon's pins.json), that carry no state.json (a spare worker dir), or whose state.json fails to parse -- malformed jobs are dropped rather than failing the whole listing. The timeline parser skips blank and malformed lines.

Example

{:ok, root} = ClaudeWrapper.Jobs.home()
{:ok, summaries} = ClaudeWrapper.Jobs.list(root)

for s <- summaries do
  IO.puts("#{s.short_id}  [#{s.state}]  #{s.intent}")
end

{:ok, job} = ClaudeWrapper.Jobs.get(root, "90c961c7")

for event <- job.timeline do
  IO.puts("#{event.at}  #{event.state}")
end

Summary

Functions

Use a specific path as the jobs root. Useful for tests (point at a temp dir) and non-default installs.

Read one job by short id (its ~/.claude/jobs/<short_id>/ directory name) into a full ClaudeWrapper.Jobs.Job, including the parsed timeline.jsonl and the raw state.json map.

Resolve the default jobs root, ~/.claude/jobs.

List every job directory at the root, sorted by short_id.

The configured root directory.

Types

t()

@type t() :: %ClaudeWrapper.Jobs{root: String.t()}

Functions

at(path)

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

Use a specific path as the jobs root. Useful for tests (point at a temp dir) and non-default installs.

get(jobs, short_id)

@spec get(t(), String.t()) ::
  {:ok, ClaudeWrapper.Jobs.Job.t()} | {:error, ClaudeWrapper.Error.t()}

Read one job by short id (its ~/.claude/jobs/<short_id>/ directory name) into a full ClaudeWrapper.Jobs.Job, including the parsed timeline.jsonl and the raw state.json map.

Returns {:error, %ClaudeWrapper.Error{kind: :not_found}} when no such directory exists or its state.json is missing.

home()

@spec home() :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}

Resolve the default jobs root, ~/.claude/jobs.

Returns {:error, %ClaudeWrapper.Error{kind: :no_home}} when the user home cannot be determined.

list(jobs)

@spec list(t()) ::
  {:ok, [ClaudeWrapper.Jobs.Summary.t()]} | {:error, ClaudeWrapper.Error.t()}

List every job directory at the root, sorted by short_id.

Returns {:ok, []} when the root directory does not exist (no background agents have been launched on this machine yet). Skips entries that are not directories, that carry no state.json, or whose state.json fails to parse.

root(jobs)

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

The configured root directory.