README.md

# ObanDoctor

[![Coverage Status](https://coveralls.io/repos/github/tomasz-tomczyk/oban_doctor/badge.svg?branch=main)](https://coveralls.io/github/tomasz-tomczyk/oban_doctor?branch=main)

Static analysis tool for Oban workers and configuration. Catches misconfigurations and worker bugs at CI time through AST parsing—no runtime dependency on Oban required.

## Installation

Add `oban_doctor` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:oban_doctor, "~> 0.1.0", only: [:dev, :test], runtime: false}
  ]
end
```

## Usage

Run all worker checks:

```bash
mix oban_doctor.check_workers
```

Options:

- `--format=json` - Output as JSON (for CI integration)
- `--strict` - Treat warnings as errors (exit code 1)

## Checks

### Worker Checks

#### MissingQueue

Detects workers using queues that are not defined in your Oban configuration. See [Oban queue configuration](https://hexdocs.pm/oban/Oban.html#module-configuring-queues).

#### StateGroupUsage

Detects workers using the `:all` state group in unique configuration. Oban provides named state groups:

- `:successful` (default) - excludes cancelled/discarded, safe for most cases
- `:incomplete` - only unfinished jobs, good for preventing concurrent execution
- `:scheduled` - only scheduled jobs, useful for debouncing
- `:all` - includes cancelled/discarded (DANGEROUS - jobs can never be re-enqueued)

See [Oban unique jobs](https://hexdocs.pm/oban/unique_jobs.html).

```elixir
# Bad - prevents re-enqueueing forever
use Oban.Worker, unique: [fields: [:args], states: :all]

# Good - use a named group
use Oban.Worker, unique: [fields: [:args], states: :incomplete]

# Good - explicit states
use Oban.Worker, unique: [fields: [:args], states: [:available, :scheduled, :executing, :retryable]]
```

#### UniquenessMissingStates

Detects workers with explicit state lists that are missing recommended states. **Missing `:retryable` is a common issue** - when a job fails and enters retryable state (with backoff), a new job with the same unique key can be enqueued, causing duplicates during deployments or scaling.

This check does NOT flag named groups (`:incomplete`, `:scheduled`, `:successful`) as these are valid Oban patterns. See [Oban unique jobs](https://hexdocs.pm/oban/unique_jobs.html).

```elixir
# Bad - missing :retryable causes duplicates during retries
use Oban.Worker, unique: [fields: [:args], states: [:available, :scheduled, :executing]]

# Good - includes all non-final states
use Oban.Worker, unique: [fields: [:args], states: [:available, :scheduled, :executing, :retryable]]

# Better - use a named group instead
use Oban.Worker, unique: [fields: [:args], states: :incomplete]
```

#### UniqueWithoutKeys

Detects workers with unique constraint on `:args` but no explicit `keys` option. Without `keys`, all args fields are used for uniqueness which may not be intentional. See [Oban unique jobs](https://hexdocs.pm/oban/unique_jobs.html).

```elixir
# Potentially problematic - uses ALL args fields
use Oban.Worker, unique: [fields: [:args]]

# Better - explicit about which args matter
use Oban.Worker, unique: [fields: [:args], keys: [:user_id, :action]]
```

#### NoMaxAttempts

Detects workers using the default `max_attempts` of 20. This is often too high and can waste resources on operations unlikely to succeed. See [Oban.Worker options](https://hexdocs.pm/oban/Oban.Worker.html#module-defining-workers).

```elixir
# Using default (20 attempts)
use Oban.Worker, queue: :default

# Explicit reasonable limit
use Oban.Worker, queue: :default, max_attempts: 3
```

## Configuration

Create a `.oban_doctor.exs` file in your project root to customize behavior. You can generate one with default settings:

```bash
mix oban_doctor.gen.config
```

This creates a config file with all available checks listed and their default severities. If a config file already exists, you'll be prompted before overwriting.

Example configuration:

```elixir
[
  checks: [
    missing_queue: [enabled: true],
    state_group_usage: [enabled: true],
    uniqueness_missing_states: [enabled: true, severity: :info],
    unique_without_keys: [enabled: false],
    no_max_attempts: [enabled: true]
  ],

  # Exclude specific workers from all checks
  excluded_workers: [
    MyApp.Workers.LegacyWorker
  ],

  # Exclude files matching these patterns
  excluded_files: [
    "test/support/"
  ]
]
```

### Configuration Options

- `enabled` - Enable or disable a check (default: `true`)
- `severity` - Override the default severity (`:error`, `:warning`, or `:info`)
- `excluded_workers` - List of worker modules to skip
- `excluded_files` - List of file path patterns to skip

## CI Integration

Add to your CI pipeline:

```bash
mix oban_doctor.check_workers --strict
```

For JSON output (useful for custom reporting):

```bash
mix oban_doctor.check_workers --format=json
```

Exit codes:

- `0` - No errors (warnings are allowed unless `--strict`)
- `1` - Errors found (or warnings with `--strict`)

## License

MIT