Run a Claude Code job (via claude_wrapper)
on an Oban queue.
oban_claude is the thin seam between Oban and claude:
run/2is the engine: a string-keyed args map in, a{oban_return, result}tuple out. It callsClaudeWrapper.query/2and maps the typed%ClaudeWrapper.Result{}/%ClaudeWrapper.Error{}onto Oban's return values via a classifier (seeObanClaude.Outcome).ObanClaude.Workeris the drop-in worker (use ObanClaude.Worker) with a singleObanClaude.Worker.handle_result/2override point.
It owns no state and runs no daemon, and takes no position on what consumes a
result. Whether the agent effects its own writes (full-auto) or returns
structured data for a downstream effector is encoded in the job's args
(prompt + permission mode), never here, so oban_claude supports both.
Example
Build the job's args with ObanClaude.Args.new/1 (atom keys, validated) rather
than a hand-written string map:
defmodule MyApp.ClaudeJob do
use ObanClaude.Worker, queue: :claude, max_attempts: 3
end
ObanClaude.Args.new(prompt: "summarize the repo", working_dir: "/path/to/repo")
|> MyApp.ClaudeJob.new()
|> Oban.insert()The raw string-keyed map is still accepted as the low-level escape hatch:
%{"prompt" => "summarize the repo", "working_dir" => "/path/to/repo"}
|> MyApp.ClaudeJob.new()
|> Oban.insert()Telemetry
run/2 emits the following events via :telemetry:
[:oban_claude, :run, :stop]
Emitted after a successful ClaudeWrapper.query/2 call. This includes
is_error: true results, which are returned without raising.
- Measurements:
:duration-- wall time of the query in native time units (convert withSystem.convert_time_unit/3):cost_usd-- the reported cost in USD from the result (0.0when the result carries no cost)
- Metadata:
:result-- the%ClaudeWrapper.Result{}struct:args-- the string-keyed args map passed torun/2
[:oban_claude, :run, :exception]
Emitted when ClaudeWrapper.query/2 returns {:error, %ClaudeWrapper.Error{}}.
- Measurements:
:duration-- wall time of the query in native time units
- Metadata:
:error-- the%ClaudeWrapper.Error{}struct:args-- the string-keyed args map passed torun/2
Summary
Types
An Oban.Worker.perform/1 return value.
Functions
Read the "outcome" string from a result's structured_output, when a
--json-schema run produced one. Returns nil otherwise.
Run a claude job from a string-keyed args map.
Return the full schema-validated structured_output from a result (a
--json-schema run), or nil when the run produced none.
Types
@type oban_return() :: :ok | {:ok, term()} | {:error, term()} | {:cancel, term()} | {:snooze, pos_integer()}
An Oban.Worker.perform/1 return value.
Functions
@spec outcome(ClaudeWrapper.Result.t()) :: String.t() | nil
Read the "outcome" string from a result's structured_output, when a
--json-schema run produced one. Returns nil otherwise.
Useful inside ObanClaude.Worker.handle_result/2 to branch on a typed
outcome (e.g. "done" vs "blocked").
@spec run( map(), keyword() ) :: {oban_return(), ClaudeWrapper.Result.t() | ClaudeWrapper.Error.t() | term()}
Run a claude job from a string-keyed args map.
Returns {oban_return, %Result{} | %Error{}} so a caller can both act on the
Oban verdict and inspect the underlying run (cost, session id, structured
output). :prompt is required; every key in @passthrough is forwarded to
ClaudeWrapper.query/2.
Options:
:classifier-- a 1-arity fun mapping the claude call's result onto{oban_return, term}. Defaults to&ObanClaude.Outcome.classify/1.:query_fun-- the claude entrypoint: a 2-arity fun(prompt, query_opts) -> {:ok, %Result{}} | {:error, %Error{}}. Defaults to&ClaudeWrapper.query/2. Override to stub claude in tests, or to route through a different wrapper entrypoint.
@spec structured(ClaudeWrapper.Result.t()) :: map() | list() | nil
Return the full schema-validated structured_output from a result (a
--json-schema run), or nil when the run produced none.
Use inside ObanClaude.Worker.handle_result/2 to branch on a typed result
object. outcome/1 is the convenience for the common "outcome" key.