Agent skill

Oban Background Jobs

by georgeguimaraes in georgeguimaraes/elixir-agent-tools

Guidance for building and debugging durable background jobs in Elixir with Oban and Oban Pro, covering serialization, retries, uniqueness, chaining, chunking and workflows.

Apache-2.0Auto-check passedBackend & APIs

Install Oban Background Jobs

skills CLI
$ npx skills add georgeguimaraes/elixir-agent-tools --skill oban -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install georgeguimaraes/elixir-agent-tools oban --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/georgeguimaraes/elixir-agent-tools.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/elixir-dev/skills/oban .claude/skills/oban && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
oban
GitHub stars
184
Token cost
~2.2k tokens
SKILL.md length
528 words
Files
1
Skills in repo
5
Repo updated
First seen
Licence
Apache-2.0

At a glance

Guidance for building and debugging durable background jobs in Elixir with Oban and Oban Pro, covering serialization, retries, uniqueness, chaining, chunking and workflows.

  • Works in 4 steps: Automatic logging: Oban logs the full… → Automatic retries: Jobs retry with… → Visibility: Failed jobs appear in Oban… → …
  • Writing an Oban worker with retries and sensible error handling
  • SKILL.md covers The Iron Law: JSON Serialization, Error Handling: Let It Crash, Snoozing for Polling and Simple Job Chaining, plus 8 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Part one covers plain Oban and opens with its main rule: job arguments are stored as JSON, so atom keys come back as strings, which the skill says causes most debugging trouble. For errors it recommends letting a job crash and bubble up to Oban, which then logs the stacktrace, retries with exponential backoff, shows the failure in the Oban Web dashboard and records the state in the database. Catching is reserved for custom retry logic or marking a job permanently failed, and a snooze return is the way to poll external state.

Other sections cover chaining simple sequences by having each job enqueue the next, without reaching for Oban Pro Workflows, and the `unique` option, which is checked at insert time, so identical jobs inserted outside the period, such as 61 seconds apart with a 60-second window, both run. For millions of records it advises chunking work into batches rather than one job per item to spare the database. The description also names Oban Pro batches and workflows, and sends in-memory tasks and process supervision to a separate `otp` skill. The excerpt ends in the chunking section.

When your agent uses it

  • Writing an Oban worker with retries and sensible error handling
  • Debugging job arguments that arrive as strings instead of atoms
  • Preventing duplicate jobs with the unique option
  • Processing a very large set of records without flooding the jobs table

Example prompts

  • “Write an Oban worker that sends a welcome email and retries on failure.”
  • “My job args show string keys in perform, so why did my atom keys stop working?”
  • “Make this worker unique per user for one minute.”
  • “Rework this import so it chunks contacts instead of inserting one job each.”

Requirements

  • An Elixir project that uses Oban

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Automatic logging: Oban logs the full error with stacktrace
  2. Automatic retries: Jobs retry with exponential backoff
  3. Visibility: Failed jobs appear in Oban Web dashboard
  4. Consistency: Error states are tracked in the database

What it can do on your machine

Read from SKILL.md and the folder at commit ac3a5a1. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are elixir).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Oban Background Jobs loads about 2.2k tokens when it runs. Until then it costs about 60 tokens; SKILL.md has 528 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~60
When it runs · the whole SKILL.md, loaded when a task matches
~2.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from georgeguimaraes/elixir-agent-tools at commit ac3a5a1, republished under its Apache-2.0 licence (© georgeguimaraes). 528 words, ~2,172 tokens.

Download SKILL.mdSave it as .claude/skills/oban/SKILL.md (or your agent's skills folder).
name
oban
description
Build and debug durable background jobs with Oban and Oban Pro. Use for workers, job argument serialization, retries, scheduled or recurring jobs, uniqueness, batches, and workflows. Use otp for in-memory tasks and process supervision.

Oban

Handle durable jobs, serialization, failures, and workflow composition with Oban.


Part 1: Oban (Non-Pro)

The Iron Law: JSON Serialization

JOB ARGS ARE JSON. ATOMS BECOME STRINGS.

This single fact causes most Oban debugging headaches.

elixir
# Creating - atom keys are fine
MyWorker.new(%{user_id: 123})

# Processing - must use string keys (JSON converted atoms to strings)
def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
  # ...
end

Error Handling: Let It Crash

Don't catch errors in Oban jobs. Let them bubble up to Oban for proper handling.

Why?
  1. Automatic logging: Oban logs the full error with stacktrace
  2. Automatic retries: Jobs retry with exponential backoff
  3. Visibility: Failed jobs appear in Oban Web dashboard
  4. Consistency: Error states are tracked in the database
Anti-Pattern
elixir
# Bad: Swallowing errors
def perform(%Oban.Job{} = job) do
  case do_work(job.args) do
    {:ok, result} -> {:ok, result}
    {:error, reason} ->
      Logger.error("Failed: #{reason}")
      {:ok, :failed}  # Silently marks as complete!
  end
end
Correct Pattern
elixir
# Good: Let errors propagate
def perform(%Oban.Job{} = job) do
  result = do_work!(job.args)  # Raises on failure
  {:ok, result}
end

# Or return error tuple - Oban treats as failure
def perform(%Oban.Job{} = job) do
  case do_work(job.args) do
    {:ok, result} -> {:ok, result}
    {:error, reason} -> {:error, reason}  # Oban will retry
  end
end
When to Catch Errors

Only catch errors when you need custom retry logic or want to mark a job as permanently failed:

elixir
def perform(%Oban.Job{} = job) do
  case external_api_call(job.args) do
    {:ok, result} -> {:ok, result}
    {:error, :not_found} -> {:cancel, :resource_not_found}  # Don't retry
    {:error, :rate_limited} -> {:snooze, 60}  # Retry in 60 seconds
    {:error, _} -> {:error, :will_retry}  # Normal retry
  end
end

Snoozing for Polling

Use {:snooze, seconds} for polling external state instead of manual retry logic:

elixir
def perform(%Oban.Job{} = job) do
  if external_thing_finished?(job.args) do
    {:ok, :done}
  else
    {:snooze, 5}  # Check again in 5 seconds
  end
end

Simple Job Chaining

For simple sequential chains (JobA → JobB → JobC), have each job enqueue the next:

elixir
def perform(%Oban.Job{} = job) do
  result = do_work(job.args)
  # Enqueue next job on success
  NextWorker.new(%{data: result}) |> Oban.insert()
  {:ok, result}
end

Don't reach for Oban Pro Workflows for linear chains.

Unique Jobs

Prevent duplicate jobs with the unique option:

elixir
use Oban.Worker,
  queue: :default,
  unique: [period: 60]  # Only one job with same args per 60 seconds

# Or scope uniqueness to specific fields
unique: [period: 300, keys: [:user_id]]

Gotcha: Uniqueness is checked on insert, not execution. Two identical jobs inserted 61 seconds apart will both run.

High Throughput: Chunking

For millions of records, chunk work into batches rather than one job per item:

elixir
# Bad: One job per contact (millions of jobs = database strain)
Enum.each(contacts, &ContactWorker.new(%{id: &1.id}) |> Oban.insert())

# Good: Chunk into batches
contacts
|> Enum.chunk_every(100)
|> Enum.each(&BatchWorker.new(%{contact_ids: Enum.map(&1, fn c -> c.id end)}) |> Oban.insert())

Use bulk inserts without uniqueness constraints for maximum throughput.


Part 2: Oban Pro

Cascade Context: Erlang Term Serialization

Unlike regular job args, cascade context preserves atoms:

elixir
# Creating - atom keys
Workflow.put_context(%{score_run_id: id})

# Processing - atom keys still work!
def my_cascade(%{score_run_id: id}) do
  # ...
end

# Dot notation works too
def later_step(context) do
  context.score_run_id
  context.previous_result
end
Serialization Summary
CreatingProcessing
Regular jobsatoms okstrings only
Cascade contextatoms okatoms ok

When to Use Workflows

Reserve Workflows for:

  • Complex dependency graphs (not just linear chains)
  • Fan-out/fan-in patterns
  • When you need recorded values across steps
  • Conditional branching based on runtime state

Don't use Workflows for simple A → B → C chains.

Workflow Composition with Graft

When you need a parent workflow to wait for a sub-workflow to complete before continuing, use add_graft instead of add_workflow.

Key Differences
MethodSub-workflow completes before deps run?Output accessible?
add_workflowNo - just inserts jobsNo
add_graftYes - waits for all jobsYes, via recorded values
Show full SKILL.md (204 more words)Show less
Pattern: Composing Independent Concerns

Don't couple unrelated concerns (e.g., notifications) to domain-specific workflows (e.g., scoring). Instead, create a higher-level orchestrator:

elixir
# Bad: Notification logic buried in AggregateScores
defmodule AggregateScores do
  def workflow(score_run_id) do
    Workflow.new()
    |> Workflow.add(:aggregate, AggregateJob.new(...))
    |> Workflow.add(:send_notification, SendEmail.new(...), deps: :aggregate)  # Wrong place!
  end
end

# Good: Higher-level workflow composes scoring + notification
defmodule FullRunWithNotifications do
  def workflow(site_url, opts) do
    notification_opts = build_notification_opts(opts)

    Workflow.new()
    |> Workflow.put_context(%{notification_opts: notification_opts})
    |> Workflow.add_graft(:scoring, &graft_full_run/1)
    |> Workflow.add_cascade(:send_notification, &send_notification/1, deps: :scoring)
  end

  defp graft_full_run(context) do
    # Sub-workflow doesn't know about notifications
    FullRun.workflow(context.site_url, context.opts)
    |> Workflow.apply_graft()
    |> Oban.insert_all()
  end
end
Recording Values for Dependent Steps

For a grafted workflow's output to be available to dependent steps, the final job must use recorded: true:

elixir
defmodule FinalJob do
  use Oban.Pro.Worker, queue: :default, recorded: true

  def perform(%Oban.Job{} = job) do
    # Return value becomes available in context
    {:ok, %{score_run_id: score_run_id, composite_score: score}}
  end
end

Dynamic Workflow Appending

Add jobs to a running workflow with Workflow.append/2:

elixir
def perform(%Oban.Job{} = job) do
  if needs_extra_step?(job.args) do
    job
    |> Workflow.append()
    |> Workflow.add(:extra, ExtraWorker.new(%{}), deps: [:current_step])
    |> Oban.insert_all()
  end
  {:ok, :done}
end

Caveat: Cannot override context or add dependencies to already-running jobs. For complex dynamic scenarios, check external state in the job itself.

Fan-Out/Fan-In with Batches

To run a final job after multiple paginated workflows complete, use Batch callbacks:

elixir
# Wrap workflows in a shared batch
batch_id = "import-#{import_id}"

pages
|> Enum.each(fn page ->
  PageWorkflow.workflow(page)
  |> Batch.from_workflow(batch_id: batch_id)
  |> Oban.insert_all()
end)

# Add completion callback
Batch.new(batch_id: batch_id)
|> Batch.add_callback(:completed, CompletionWorker)
|> Oban.insert()

Tip: Include pagination workers in the batch to prevent premature completion.

Testing Workflows

Don't use inline testing mode - workflows need database interaction.

elixir
# Use run_workflow/1 for integration tests
assert %{completed: 3} =
  Workflow.new()
  |> Workflow.add(:a, WorkerA.new(%{}))
  |> Workflow.add(:b, WorkerB.new(%{}), deps: [:a])
  |> Workflow.add(:c, WorkerC.new(%{}), deps: [:b])
  |> run_workflow()

For testing recorded values between workers, insert predecessor jobs with pre-filled metadata.


Red Flags - STOP and Reconsider

Non-Pro:

  • Pattern matching on atom keys in perform/1
  • Catching all errors and returning {:ok, _}
  • Wrapping job logic in try/rescue
  • Creating one job per item when processing millions of records

Pro:

  • Using add_workflow when you need to wait for completion
  • Coupling notifications/emails to domain workflows
  • Not using recorded: true when you need output from grafted workflows
  • Using Workflows for simple linear job chains
  • Testing workflows with inline mode

Any of these? Re-read the serialization rules.

© georgeguimaraes, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in plugins/elixir-dev/skills/oban of georgeguimaraes/elixir-agent-tools.

Open the folder on GitHubat commit ac3a5a1

Compare with similar skills

Oban Background Jobs next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Oban Background Jobs compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Oban Background Jobs this skillgeorgeguimaraes/elixir-agent-tools184—~2.2kAutomated safety check: PassApache-2.0
Inngest Durable FunctionsAsymmetric-al/core381—~4kAutomated safety check: PassAGPL-3.0
Cloudflare Queuessecondsky/claude-skills227—~4kAutomated safety check: PassMIT
Robust Error Handling In Scriptsaiming-lab/MetaClaw3.5k—~225Automated safety check: PassMIT
Elixir AntipatternsGentleman-Programming/Gentleman-Skills658—~2.1kAutomated safety check: PassMIT
Node Backend Development Guidelinesdiet103/claude-code-infrastructure-showcase10k2 repos~2kAutomated safety check: PassMIT

Similar skills

  • Inngest Durable Functions

    Asymmetric-al/core

    A skill your agent uses when building functions that must survive process crashes, retry automatically on failure, run on a schedule, react to events, or maintain state across infrastructure…

    381 GitHub stars~4k tokensUpdated yesterday
    Backend & APIsAuto-check passed
  • Cloudflare Queues

    secondsky/claude-skills

    This skill should be used when the user asks to "set up Cloudflare Queues", "create a message queue", "implement queue consumer", "process background jobs", "configure queue retry logic", "publish…

    227 GitHub stars~4k tokensUpdated 12 days ago
    Backend & APIsAuto-check passed
  • A skill your agent uses when writing shell scripts, Python automation, or any unattended batch job.

    3.5k GitHub stars~225 tokensUpdated 4 mo ago
    DevelopmentAuto-check passed
  • Elixir Antipatterns

    Gentleman-Programming/Gentleman-Skills

    Core catalog of 8 critical Elixir/Phoenix anti-patterns covering error handling, separation of concerns, Ecto queries, and testing.

    658 GitHub stars~2.1k tokensUpdated 6 mo ago
    DevelopmentAuto-check passed
  • Node Backend Development Guidelines

    diet103/claude-code-infrastructure-showcase

    Sets layered architecture and coding rules for Node.js, Express and TypeScript microservices, covering routes, controllers, services, repositories, Prisma, Sentry and Zod.

    10k GitHub starsUsed in 2 repos~2k tokens
    Backend & APIsAuto-check passed
  • Sentry v8 Error Tracking

    diet103/claude-code-infrastructure-showcase

    Enforces that every error in a service is captured to Sentry v8, with patterns for controllers, routes, cron jobs and database performance spans instead of console logging alone.

    10k GitHub starsUsed in 2 repos~2.3k tokens
    Backend & APIsAuto-check: notes

More from georgeguimaraes/elixir-agent-tools

  • Ecto Persistence Patterns

    georgeguimaraes/elixir-agent-tools

    Designs and debugs Elixir persistence with Ecto: schemas, changesets, queries, preloads, migrations and multi-tenancy, kept within application contexts.

    184 GitHub stars~1.2k tokensUpdated 21 days ago
    Auto-check passed
  • Idiomatic Elixir

    georgeguimaraes/elixir-agent-tools

    Writes and refactors idiomatic Elixir modules and functions, with rules for pattern matching, error handling, protocols and when a process is really needed.

    184 GitHub stars~2.1k tokensUpdated 21 days ago
    Auto-check passed
  • Elixir OTP Concurrency

    georgeguimaraes/elixir-agent-tools

    Guides choices among GenServer, Supervisor, Task, Registry, ETS and Broadway when designing or debugging Elixir concurrency, state and fault recovery.

    184 GitHub stars~1.2k tokensUpdated 21 days ago
    Auto-check passed
  • Phoenix LiveView Patterns

    georgeguimaraes/elixir-agent-tools

    Guidance for building and debugging Phoenix web interfaces: where LiveView loads data, scopes, PubSub topics, external polling and component state.

    184 GitHub stars~1.3k tokensUpdated 21 days ago
    Auto-check passed

Works with

Categories

Questions about Oban Background Jobs

What does Oban Background Jobs do?

Guidance for building and debugging durable background jobs in Elixir with Oban and Oban Pro, covering serialization, retries, uniqueness, chaining, chunking and workflows. Part one covers plain Oban and opens with its main rule: job arguments are stored as JSON, so atom keys come back as strings, which the skill says causes most debugging trouble. For errors it recommends letting a job crash and bubble up to Oban, which then logs the stacktrace, retries with exponential backoff, shows the failure in the Oban Web dashboard and records the state in the database.

When should I use Oban Background Jobs?

Oban Background Jobs fits situations like: writing an Oban worker with retries and sensible error handling; debugging job arguments that arrive as strings instead of atoms; preventing duplicate jobs with the unique option; processing a very large set of records without flooding the jobs table.

How do I install Oban Background Jobs in Claude Code?

Run `npx skills add georgeguimaraes/elixir-agent-tools --skill oban -a claude-code`. Or copy the skill folder (plugins/elixir-dev/skills/oban in georgeguimaraes/elixir-agent-tools) into .claude/skills/oban in your project. Claude Code loads it when a task matches its description.

How do I install Oban Background Jobs in Codex?

Run `npx skills add georgeguimaraes/elixir-agent-tools --skill oban -a codex`. Or copy the skill folder (plugins/elixir-dev/skills/oban in georgeguimaraes/elixir-agent-tools) into .agents/skills/oban in your project. Codex loads it when a task matches its description.

Can I use Oban Background Jobs in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add georgeguimaraes/elixir-agent-tools --skill oban -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/oban, .gemini/skills/oban, .github/skills/oban and .opencode/skills/oban in your project.

What does Oban Background Jobs need to run?

SKILL.md names no scripts, command-line tools or credentials: Oban Background Jobs is instructions for the agent only. Our summary lists: An Elixir project that uses Oban.

Does Oban Background Jobs access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Oban Background Jobs safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Oban Background Jobs use?

Oban Background Jobs is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Oban Background Jobs use?

About 2.2k tokens (SKILL.md is roughly 8.7k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Oban Background Jobs?

Skills that share tags, products or a category with Oban Background Jobs: Inngest Durable Functions (Asymmetric-al/core, 381 stars), Cloudflare Queues (secondsky/claude-skills, 227 stars), Robust Error Handling In Scripts (aiming-lab/MetaClaw, 3.5k stars) and Elixir Antipatterns (Gentleman-Programming/Gentleman-Skills, 658 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Oban Background Jobs?

georgeguimaraes (a GitHub user) maintains it in georgeguimaraes/elixir-agent-tools, which has 184 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on September 20, 2026.

Source: georgeguimaraes/elixir-agent-tools on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.