ObanClaude.Args (oban_claude v0.2.0)

Copy Markdown View Source

Build a claude job's args map without knowing claude_wrapper or the CLI.

new/1 is the first-class front door: a keyword list with atom keys and native Elixir values in, the string-keyed JSON map Oban stores out. It is the preferred way to construct args; ObanClaude.run/2 and ObanClaude.Worker still accept a raw string-keyed map directly as the low-level escape hatch.

ObanClaude.Args.new(prompt: "summarize the repo",
                    working_dir: "/repo",
                    permission_mode: :plan)
#=> %{"prompt" => "summarize the repo",
#     "working_dir" => "/repo",
#     "permission_mode" => "plan"}

The result is a plain map, so it drops straight into a worker:

MyApp.ClaudeJob.new(ObanClaude.Args.new(prompt: "...")) |> Oban.insert()

Worker defaults

defaults/1 is the same builder with :prompt optional, for the worker's :args (the constant, prompt-less config). It evaluates at compile time, so it works directly in the use:

use ObanClaude.Worker,
  queue: :claude,
  args: ObanClaude.Args.defaults(working_dir: ".", model: "sonnet",
                                 permission_mode: :bypass_permissions)

Job metadata

The :meta option carries non-claude values (an issue number, a correlation id) through to the Oban job args, where handle_result/2 and telemetry can read them. It is merged flat into the map (keys stringified) and is not validated as a claude option:

ObanClaude.Args.new(prompt: "...", meta: %{"issue" => "173"})
#=> %{"prompt" => "...", "issue" => "173"}

Because it is merged flat, :meta has two guardrails (both raise at build time):

  • A meta key may not collide with a claude option name (or "prompt") -- otherwise it would become an unvalidated query option and could override a worker default across the merge. Rename the key, or set the option directly.
  • Meta values must be JSON-encodable (Oban stores args as JSON). A tuple or a struct without a Jason.Encoder raises here, naming the key, rather than deep inside Oban.insert.

One caveat the guardrails cannot catch: an atom meta value is valid JSON but round-trips back as a string (:high in, "high" out). Hand-built %Oban.Job{args: ...} test jobs skip that round-trip, so a handle_result/2 clause matching on the atom passes in tests and silently never matches in production. Use string meta values, and prefer Oban.Testing.perform_job/3 (which JSON-recodes args) over a hand-built job in worker tests.

Validation

Unlike the raw-map path (which forwards known keys and silently ignores the rest), the builder validates against the schema below: unknown keys raise, and each option's type (including the :permission_mode and :effort vocabularies) is checked. Errors surface at construction / enqueue time rather than when the job runs.

Options

  • :prompt (String.t/0) - Required. The claude prompt. The only required option for new/1.

  • :model (String.t/0) - Model name, e.g. "sonnet" or "opus".

  • :fallback_model (String.t/0) - Model to fall back to if the primary is unavailable.

  • :working_dir (String.t/0) - Directory claude runs in.

  • :binary (String.t/0) - Path to the claude CLI binary. Pin a known-good version per worker (e.g. in defaults/1) so a PATH auto-update mid-batch can't drift the run -- pairs well with hermetic. (The wrapper's :bundled mode is not exposed here; use a custom :query_fun for that.)

  • :add_dir - Extra directories claude may access -- a single path or a list of paths.

  • :system_prompt (String.t/0) - Replace claude's system prompt.

  • :append_system_prompt (String.t/0) - Append to claude's system prompt.

  • :permission_mode - How claude handles tool-permission prompts.

  • :allowed_tools (list of String.t/0) - Whitelist of tools claude may use.

  • :disallowed_tools (list of String.t/0) - Blacklist of tools claude may not use.

  • :mcp_config (list of String.t/0) - MCP server config file paths.

  • :agent (String.t/0) - Named agent/subagent to run as.

  • :effort - Reasoning effort level.

  • :max_turns (pos_integer/0) - Cap on agent turns in the run.

  • :max_budget_usd - Cost ceiling for the run, in USD.

  • :timeout (pos_integer/0) - Subprocess timeout in milliseconds.

  • :json_schema (String.t/0) - An inline JSON Schema string for structured output, passed verbatim to the CLI's --json-schema (not a file path -- read the file yourself and pass its contents).

  • :worktree - Run in a git worktree (--worktree). true for an ephemeral worktree, a string for a named one (reusable across jobs -- e.g. "issue-173"). Requires working_dir to be a git repo. Recommended for full-auto workers that write to a repo -- set it in defaults/1.

  • :hermetic - Seal the ambient ~/.claude config so the run's surface is exactly what these args set (--setting-sources, --strict-mcp-config, --exclude-dynamic-system-prompt-sections; auth untouched). true is an alias for :full. :full drops user + project + local ambient config; :project seals project + local but keeps the user's global ~/.claude. Recommended in defaults/1 for reproducible server runs that should not depend on the host's config.

  • :resume (String.t/0) - Resume a prior claude session by id (--resume). The id comes from a previous run -- ObanClaude.session_id/1 reads it off a %Result{} or a rail-stop %Error{}. The canonical use is the resume-after-rail-stop recipe: a follow-on job with resume: + the same named worktree continues a stopped run cheaply. Session state is node-local (see the "session-resume handoff" pattern in the Agent worker patterns guide).

  • :session_id (String.t/0) - Pin the session id for this run (--session-id), so a known id can be resumed later. Distinct from resume, which continues an existing id.

  • :no_session_persistence (boolean/0) - Do not persist this run's session transcript under ~/.claude (--no-session-persistence). Recommended for fire-and-forget batch workers: it stops unattended runs accumulating transcripts (disk growth and plaintext data at rest). Mutually exclusive with the resume recipe -- it deletes the transcript resume would need.

  • :fork_session (boolean/0) - Fork a new session from the resumed one rather than continuing it in place (--fork-session), so the original transcript is left untouched.

  • :meta - Non-claude metadata merged into the args map untouched (keys stringified). For values handle_result/2 or telemetry needs -- an issue number, a correlation id. Explicit claude options win on a key collision. Note: meta set as a worker default (via defaults/1) reaches telemetry (the merged args) but NOT handle_result/2, which sees only the job's own args -- put meta a handler reads on the job, not the worker default.

Summary

Types

t()

The string-keyed args map the builder produces and ObanClaude.run/2 consumes -- what Oban serializes as the job's args. Keys are strings (claude options plus any stringified :meta); values are JSON-clean.

Functions

Build a worker-level defaults map: the same as new/1, but :prompt is optional (worker :args are the prompt-less config the per-job args fill in).

The options the builder accepts.

Build a string-keyed args map from a keyword list of atom-keyed options.

Types

t()

@type t() :: %{optional(String.t()) => term()}

The string-keyed args map the builder produces and ObanClaude.run/2 consumes -- what Oban serializes as the job's args. Keys are strings (claude options plus any stringified :meta); values are JSON-clean.

Functions

defaults(opts \\ [])

@spec defaults(keyword()) :: t()

Build a worker-level defaults map: the same as new/1, but :prompt is optional (worker :args are the prompt-less config the per-job args fill in).

Because it evaluates at compile time, it can be used directly in the worker's use (see "Worker defaults" above).

keys()

@spec keys() :: [atom()]

The options the builder accepts.

new(opts)

@spec new(keyword()) :: t()

Build a string-keyed args map from a keyword list of atom-keyed options.

Validates opts against the schema documented above (see "Options"). Raises NimbleOptions.ValidationError on a missing :prompt, an unknown key, or a value of the wrong type. Returns the map Oban serializes as the job's args.