ObanClaude.Outcome (oban_claude v0.1.0)

Copy Markdown View Source

The default mapping from claude_wrapper's typed result/error onto Oban's return values. Override per-worker with use ObanClaude.Worker, classifier: &MyMod.classify/1.

The mapping encodes the right Oban semantics for each claude failure mode:

claude outcomeObanwhy
%Result{is_error: false}{:ok, result}success; the worker's handle_result/2 decides the final atom
%Result{is_error: true}{:error, ...}claude emitted an error result with valid JSON; retry, capped by max_attempts
%Error{kind: :timeout}{:snooze, 30}transient; back off and retry soon
%Error{kind: :command_failed/:json/:io}{:error, kind}likely transient infra; retry with backoff
a config/env fault (see below){:cancel, kind}the same broken environment re-fails identically; a retry only burns budget
%Error{kind: :budget_exceeded/:max_turns_exceeded}{:cancel, kind}the rails stopped it; resuming is a deliberate act, not a blind retry
any other typed %Error{}{:error, kind}unknown but typed; retry under max_attempts, then dead-letter
a non-%Error{} error term{:cancel, ...}off-contract and unclassifiable; do not blindly re-run

The config/env faults that cancel are :auth, :binary_not_found, :version_mismatch, :invalid_version, :dangerous_not_allowed, :invalid_tool_pattern, :not_a_git_repo, and :git_unavailable: a missing or unauthenticated binary, an unusable CLI version, a disallowed flag, a malformed tool pattern, or a worktree run against a non-git directory or a host without git fails the same way on every attempt, so retrying cannot help and only delays the dead-letter.

The :budget_exceeded and :max_turns_exceeded rows are the genuinely app-dependent ones -- an app whose worker resumes via a pinned --session-id may prefer {:snooze, n} or {:error, ...} there. That is exactly what the :classifier override is for.

An {:error, term} that is not a typed %Error{} (off the documented contract, e.g. a custom :query_fun routing through ClaudeWrapper.Structured.run/3, whose parse/1 and Jason paths return non-Error terms) is cancelled rather than retried: an unclassifiable failure should not be blindly re-run.

Summary

Functions

Map ClaudeWrapper.query/2's return onto {oban_return, term}.

Functions

classify(arg)

@spec classify({:ok, ClaudeWrapper.Result.t()} | {:error, term()}) ::
  {ObanClaude.oban_return(), ClaudeWrapper.Result.t() | term()}

Map ClaudeWrapper.query/2's return onto {oban_return, term}.