Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Troubleshooting

This page collects common setup, runtime, and documentation issues with concrete checks you can run before digging into application-specific code.

Database Connection Problems

Graphile Worker RS needs a PostgreSQL connection. Most examples use a SQLx pool and most local checks use DATABASE_URL.

Check that the URL is set and points at PostgreSQL:

echo "$DATABASE_URL"

For a local throwaway database that matches the test setup, the repository uses PostgreSQL on port 54233:

docker run -d --name graphile-worker-rs-test \
  -p 54233:5432 \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_DB=postgres \
  postgres

Then use:

export DATABASE_URL='postgres://postgres:postgres@localhost:54233/postgres'

If a command cannot connect, verify that the container is ready:

docker exec graphile-worker-rs-test pg_isready -U postgres -h localhost

Migrations Have Not Run

If jobs are not visible, worker startup fails, or CLI commands fail against an empty database, run the Graphile Worker migrations for the target database.

Using the CLI:

graphile-worker --database-url postgres://postgres:postgres@localhost/postgres migrate

Or with DATABASE_URL:

DATABASE_URL=postgres://postgres:postgres@localhost/postgres graphile-worker migrate

By default, the CLI uses the graphile_worker schema. See Migrations and CLI for more operational detail.

Runtime Or Feature Mismatches

The default crate features are Tokio, rustls TLS, and the SQLx driver:

graphile_worker = { version = "0.13" }

If you disable default features, choose one runtime and one supported driver. For async-std with SQLx, include the async-std runtime feature and a TLS feature:

graphile_worker = { version = "0.13", default-features = false, features = ["runtime-async-std", "driver-sqlx", "tls-rustls"] }
async-std = { version = "1", features = ["attributes"] }

For Tokio with the tokio-postgres driver:

graphile_worker = { version = "0.13", default-features = false, features = ["runtime-tokio", "driver-tokio-postgres"] }

The repository test matrix only runs driver-tokio-postgres with runtime-tokio. If you see feature errors, compare your feature list with Runtime and Drivers and Features.

Worker Starts But A Job Does Not Run

First check that the task identifier used when adding the job exactly matches the registered handler:

impl TaskHandler for SendEmail {
    const IDENTIFIER: &'static str = "send_email";

    async fn run(self, _ctx: WorkerContext) -> impl IntoTaskHandlerResult {
        Ok::<(), String>(())
    }
}

The worker must register that handler before init():

let worker = WorkerOptions::default()
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;

Then add jobs with the same identifier or with the type-safe helper:

worker
    .create_utils()
    .add_raw_job(
        "send_email",
        serde_json::json!({ "to": "user@example.com" }),
        Default::default(),
    )
    .await?;

If jobs are scheduled for the future, they will not run until their run_at time. If jobs share a queue name, jobs in that queue run in series rather than in parallel. See Tasks, Scheduling, and Queues.

Payload Deserialization Failures

Task payloads are deserialized into the task struct. If the payload shape does not match the struct, the handler cannot receive the data you expect.

Check the struct fields and the JSON payload side by side:

#[derive(Deserialize, Serialize)]
struct SendEmail {
    to: String,
    subject: String,
    body: String,
}
SELECT graphile_worker.add_job(
    'send_email',
    json_build_object(
        'to', 'user@example.com',
        'subject', 'Welcome',
        'body', 'Thanks for signing up.'
    )
);

When adding jobs from Rust, prefer the type-safe add_job path when the task type is available:

utils.add_job(
    SendEmail {
        to: "user@example.com".to_string(),
        subject: "Welcome".to_string(),
        body: "Thanks for signing up.".to_string(),
    },
    Default::default(),
).await?;

Shutdown Signal Conflicts

By default, Graphile Worker installs OS-level shutdown listeners such as SIGINT and SIGTERM so it can drain gracefully. If your application already owns shutdown handling, disable the built-in listeners and pass your own shutdown future:

let shutdown = WorkerShutdownConfig::default()
    .listen_os_shutdown_signals(false)
    .shutdown_signal(on_shutdown());

let worker = WorkerOptions::default()
    .worker_shutdown(shutdown)
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;

See Shutdown for the configuration surface.

Locked Jobs After A Crash

Worker recovery is disabled by default. For deployments where jobs may remain locked after a process crash, network partition, forced abort, or orchestrator shutdown, enable recovery:

let recovery = WorkerRecoveryConfig::default()
    .enabled(true)
    .heartbeat_interval(Duration::from_secs(30))
    .sweep_interval(Duration::from_secs(60))
    .sweep_threshold(Duration::from_secs(300))
    .recovery_delay(Duration::from_secs(30));

let worker = WorkerOptions::default()
    .worker_recovery(recovery)
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;

The CLI can also inspect and recover stale workers:

graphile-worker sweep-stale-workers --dry-run
graphile-worker sweep-stale-workers --sweep-threshold 5m --recovery-delay 30s

Use --dry-run first when you want to see which workers would be recovered without unlocking or rescheduling jobs. See Recovery.

CLI Checks

The installed binary is graphile-worker. It connects with --database-url or DATABASE_URL.

Useful checks:

graphile-worker list --state ready
graphile-worker show 123
graphile-worker stats
graphile-worker queues
graphile-worker workers

Useful repair commands:

graphile-worker fail 125 --reason "invalid payload"
graphile-worker reschedule 126 --run-at 2026-01-02T03:04:05Z
graphile-worker force-unlock graphile_worker_deadbeef
graphile-worker cleanup

Use the same database URL and schema that your worker uses, otherwise the CLI will inspect a different queue.

Local Test Environment Checks

The repository has two common test paths:

just test

This runs cargo test --all and requires DATABASE_URL to point at a usable PostgreSQL database.

just test-docker

This starts a postgres container named graphile-worker-rs-test, waits for pg_isready, runs tests with DATABASE_URL='postgres://postgres:postgres@localhost:54233/postgres', and then removes the container.

If Docker reports that the container name or port is already in use, remove the old test container or stop the process using port 54233:

docker rm -f graphile-worker-rs-test

Runtime-specific test helpers also exist:

just test-docker-runtime runtime-tokio driver-sqlx
just test-docker-runtime runtime-async-std driver-sqlx
just test-docker-runtime runtime-tokio driver-tokio-postgres

Documentation Build Checks

Documentation commands are defined in the repository justfile:

just docs-install
just docs
just docs-serve

just docs-install installs mdbook with version ^0.4. just docs runs mdbook build docs. just docs-serve serves the book on 127.0.0.1:3000 by default.