tobi/delayed_job
Database backed asynchronous priority queue -- Extracted from Shopify
Stale — last commit 6y ago
worst of 4 axeslast commit was 6y ago; no CI workflows detected
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
last commit was 6y ago; no CI workflows detected
- ✓28+ active contributors
- ✓Distributed ownership (top contributor 37% of recent commits)
- ✓MIT licensed
Show 3 more →Show less
- ✓Tests present
- ⚠Stale — last commit 6y ago
- ⚠No CI workflows detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days
- →Deploy as-is Mixed → Healthy if: 1 commit in the last 180 days
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/tobi/delayed_job)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/tobi/delayed_job on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: tobi/delayed_job
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/tobi/delayed_job shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Stale — last commit 6y ago
- 28+ active contributors
- Distributed ownership (top contributor 37% of recent commits)
- MIT licensed
- Tests present
- ⚠ Stale — last commit 6y ago
- ⚠ No CI workflows detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live tobi/delayed_job
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/tobi/delayed_job.
What it runs against: a local clone of tobi/delayed_job — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in tobi/delayed_job | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 2040 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of tobi/delayed_job. If you don't
# have one yet, run these first:
#
# git clone https://github.com/tobi/delayed_job.git
# cd delayed_job
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of tobi/delayed_job and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "tobi/delayed_job(\\.git)?\\b" \\
&& ok "origin remote is tobi/delayed_job" \\
|| miss "origin remote is not tobi/delayed_job (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/delayed_job.rb" \\
&& ok "lib/delayed_job.rb" \\
|| miss "missing critical file: lib/delayed_job.rb"
test -f "lib/delayed/job.rb" \\
&& ok "lib/delayed/job.rb" \\
|| miss "missing critical file: lib/delayed/job.rb"
test -f "lib/delayed/worker.rb" \\
&& ok "lib/delayed/worker.rb" \\
|| miss "missing critical file: lib/delayed/worker.rb"
test -f "lib/delayed/message_sending.rb" \\
&& ok "lib/delayed/message_sending.rb" \\
|| miss "missing critical file: lib/delayed/message_sending.rb"
test -f "lib/delayed/performable_method.rb" \\
&& ok "lib/delayed/performable_method.rb" \\
|| miss "missing critical file: lib/delayed/performable_method.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 2040 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~2010d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/tobi/delayed_job"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Delayed::Job is a Ruby library that provides a database-backed asynchronous job queue extracted from Shopify's production system. It stores job objects as YAML in a PostgreSQL/MySQL table and executes them asynchronously via background workers with configurable retry logic, priority queuing, and failure handling. Monolithic library structure: lib/delayed/ contains the core Job class (lib/delayed/job.rb), Worker class (lib/delayed/worker.rb), MessageSending and PerformableMethod for different job types, with generators/ providing Rails migration scaffolding and lib/tasks/ exposing rake tasks for queue management.
👥Who it's for
Rails developers building applications that need to offload long-running tasks (email newsletters, image resizing, API calls, search indexing) to background workers without blocking web requests. Used by teams managing multi-machine job processing at scale.
🌱Maturity & risk
This is a mature, production-hardened library extracted directly from Shopify's infrastructure circa 2008-2010. The codebase shows 32K lines of Ruby with a full spec suite (spec/ directory), migration generators, and rake tasks. However, the repository appears to be from an earlier Rails era—last visible activity suggests it may no longer be actively maintained by the core team, though it has spawned many community forks and derivatives.
The library has minimal external dependencies (fits the Rails stack natively), reducing supply-chain risk. Main risks: single-maintainer history from Shopify days, no visible CI configuration in file list, and the codebase predates modern Ruby/Rails versions—upgrading to current Rails may require adapter updates. YAML deserialization of job handlers poses security risks if untrusted data enters the queue.
Active areas of work
No recent activity visible in the provided file list. This appears to be a stable, archived library—the main development has likely moved to community-maintained forks like delayed_job_active_record, sidekiq, or resque. It serves primarily as a reference implementation.
🚀Get running
git clone https://github.com/tobi/delayed_job.git
cd delayed_job
# Install using Bundler (gemspec provided)
bundle install
# Generate migration in a Rails app
rails generate delayed_job
rake db:migrate
Daily commands:
This is a library, not an application. To use it: (1) Add gem to Gemfile, (2) Run rails generate delayed_job to create schema, (3) Create a job class with perform method, (4) Queue it via YourJob.new.delay.perform, (5) Start worker with rake jobs:work (from lib/tasks/jobs.rake).
🗺️Map of the codebase
lib/delayed_job.rb— Main entry point and public API that initializes the delayed_job gem and exposes core functionality.lib/delayed/job.rb— Core Job model representing queued tasks; defines schema, enqueueing, and execution logic that all other components depend on.lib/delayed/worker.rb— Worker process that polls the job table and executes dequeued jobs; the heart of the asynchronous execution engine.lib/delayed/message_sending.rb— Integration layer that patches Ruby's Message object to enable transparent delayed method calls on any object.lib/delayed/performable_method.rb— Serializable wrapper that captures a method call and its arguments for later execution; enables YAML persistence of jobs.generators/delayed_job_migration/delayed_job_migration_generator.rb— Rails generator that scaffolds the delayed_jobs database table; required initial setup step.lib/tasks/jobs.rake— Rake tasks for managing worker processes (start, stop, restart); critical for production deployment.
🧩Components & responsibilities
- Job Model (ActiveRecord, SQL) — Manages job lifecycle: create, enqueue, find, deserialize, mark complete/failed. Stores handler YAML and metadata.
- Failure mode: Corruption of YAML handler or database constraint violations prevent job execution; orphaned jobs accumulate in table.
- PerformableMethod (YAML, Ruby introspection) — Captures a method name, target object, and arguments; serializable wrapper that restores and executes the original method call.
- Failure mode: If object no longer exists or method signature changes, deserialization or execution fails; job enters failed state.
- Worker (ActiveRecord, Thread/Process management) — Polls job table, deserializes handlers, executes methods, catches exceptions, updates job status, and reschedules on failure.
- Failure mode: Worker crash leaves jobs in 'locked' or 'running' state; manual intervention to reset attempts or reschedule; no automatic recovery.
- Message Sending (Monkey Patch) (Ruby metaprogramming) — Extends Object#delay and Module#delay to transparently wrap method calls into PerformableMethod for enqueueing.
- Failure mode: Patch conflicts with other gems; incorrect method call capture if method takes blocks.
🔀Data flow
Rails App→Job Model— App calls MyClass.delay.method_name(args) via patched Object#delay, which routes to Job.enqueue.Job Model→Database— Job#enqueue serializes PerformableMethod to YAML and inserts row into delayed_jobs table.Worker Process→Database— Worker continuously polls SELECT * FROM delayed_jobs WHERE run_at <= NOW ORDER BY priority LIMIT 1.Database→Worker Process— Database returns job row with YAML-encoded handler; Worker deserializes and executes.Worker Process→Rails App— Worker instantiates deserialized object and invokes original method on it (e.g., MyClass#send_email).Worker Process→Database— On success, Worker deletes job row; on failure, Worker updates attempts, last_error, and run_at columns.
🛠️How to make changes
Enqueue a New Delayed Job
- Call .delay on any object with a method to defer: MyClass.delay.long_running_method(arg1, arg2) (
lib/delayed/message_sending.rb) - The method call is wrapped in PerformableMethod, serialized to YAML, and inserted into delayed_jobs table via Job.enqueue (
lib/delayed/job.rb) - Worker picks up the job, deserializes the PerformableMethod, and calls .perform to execute the original method (
lib/delayed/worker.rb)
Start a Worker Process
- Initialize the database with migration: script/generate delayed_job (
generators/delayed_job_migration/delayed_job_migration_generator.rb) - Run rake delayed_job:start to spawn a worker process (
lib/tasks/jobs.rake) - Worker enters polling loop in Worker#start, fetching jobs with priority and run_at ordering (
lib/delayed/worker.rb)
Handle Job Failure & Retry
- Job.enqueue sets attempts=0 and stores handler; worker increments attempts on each failure (
lib/delayed/job.rb) - On exception, Worker rescues, increments attempts, and updates last_error column with failure reason (
lib/delayed/worker.rb) - Job is re-enqueued with exponential backoff via run_at timestamp until max attempts exceeded (
lib/delayed/job.rb)
🔧Why these technologies
- Database-backed queue (active_record + SQL) — Simplicity and durability: jobs persist across restarts; no separate queue infrastructure required; leverages existing Rails database.
- YAML serialization for handlers — Portable method call representation that can be restored and executed later; no external dependencies.
- Polling worker pattern — Simple to understand and deploy; no message broker needed; easy to scale by running multiple worker processes.
⚖️Trade-offs already made
-
Polling-based job pickup vs. push/subscribe
- Why: Reduces infrastructure complexity and external dependencies; keeps everything within Rails/database.
- Consequence: Higher latency (poll interval) and potential database load under high job volume; not optimal for real-time jobs.
-
YAML serialization vs. structured format
- Why: Ruby-native and convenient for Rails objects; allows serializing arbitrary method calls transparently.
- Consequence: YAML deserialization security concerns if untrusted input; less portable across languages.
-
In-process worker vs. separate daemon
- Why: Simpler to manage; rake tasks handle process lifecycle.
- Consequence: Single worker per process; scaling requires manual process management rather than auto-scaling.
🚫Non-goals (don't propose these)
- Does not provide real-time job execution; poll-based design introduces latency.
- Does not handle distributed transactions or cross-database consistency.
- Does not support non-SQL backends; tightly coupled to ActiveRecord.
- Does not include job monitoring dashboard or web UI.
- Does not guarantee exactly-once execution; can have duplicate attempts on worker crash.
⚠️Anti-patterns to avoid
- YAML.load without safe mode (High) —
lib/delayed/job.rb: Deserialization of untrusted YAML in job handlers could allow arbitrary code execution if job table is compromised; should use YAML.safe_load or marshal. - Blocking polling loop in single-threaded worker (Medium) —
lib/delayed/worker.rb: Worker sleeps between polls; no concurrent job execution; all time spent sleeping is wasted capacity if jobs are short-lived. - No jitter in retry backoff —
lib/delayed/job.: undefined
🪤Traps & gotchas
YAML deserialization: job handlers are unserialized from text—never trust untrusted data in the queue; use namespaced classes to avoid arbitrary code loading. Schema assumptions: code expects specific column names (locked_at, locked_by, failed_at, handler); custom migrations may break workers. Clock skew: run_at scheduling relies on database server time—distributed workers must have synchronized clocks. Long-running jobs: MAX_RUN_TIME default is 4 hours; jobs exceeding this may be picked up by another worker, causing duplicate execution.
🏗️Architecture
💡Concepts to learn
- Exponential backoff with polynomial growth — Delayed_job's retry formula (5s + N^4) is a key characteristic; understanding why N^4 (not 2^N) matters for long-tail retry windows affects how you tune MAX_ATTEMPTS
- Optimistic locking via timestamp columns — The locked_at and locked_by columns implement row-level locking without database locks; critical for preventing duplicate execution across distributed workers
- YAML serialization and deserialization — Job objects are stored as YAML text in the handler column and reconstructed at execution time; understanding YAML safety is essential to avoid code injection vulnerabilities
- Priority queue with single-table polling — The priority column allows job prioritization without separate queue tables; workers poll with WHERE priority DESC, run_at < now ORDER BY priority DESC queries
- Worker pool pattern with heartbeat locking — Multiple worker processes compete to claim jobs via locked_at updates; understanding how heartbeat timeouts (MAX_RUN_TIME) detect dead workers is crucial for fault tolerance
- Method object pattern and dynamic dispatch — PerformableMethod wraps arbitrary method calls for late binding; the Message.method(:name) pattern enables the ergonomic delay.method_name syntax
- ActiveRecord monkey-patching for DSL extension — lib/delayed/message_sending.rb adds Method#delay dynamically; understanding this pattern shows how Rails libraries extend core classes without subclassing
🔗Related repos
collectiveidea/delayed_job_active_record— Official community-maintained fork that modernizes delayed_job for current Rails versions with ActiveRecord backend supportmperham/sidekiq— Modern Redis-backed job queue that replaced delayed_job in many Rails apps; more performant for high-volume queues but requires Redisresque/resque— Alternative Redis-based queue with simpler API; predecessor inspiration for many job queue libraries in the Ruby ecosystemjavan/whenever— Companion library for scheduling recurring jobs that queue tasks into delayed_job; often used alongside for cron-like behaviorruby-concurrency/concurrent-ruby— Low-level concurrency primitives used by modern job queue implementations; relevant for understanding worker pool patterns
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive error handling and retry logic tests in spec/job_spec.rb
The README mentions that the attempts column 'Provides for retries, but still fail eventually' but there are no visible tests validating retry behavior, exponential backoff, or final failure handling. This is critical for a background job queue where reliability is paramount. New contributors can add tests for: max retry attempts, failure callbacks, and dead letter handling.
- [ ] Review lib/delayed/job.rb to understand current retry implementation
- [ ] Add test cases in spec/job_spec.rb for max_attempts exceeded scenario
- [ ] Add test cases for retry delay/backoff logic
- [ ] Add test cases for job failure callbacks and error logging
- [ ] Verify tests pass with current implementation
Add integration tests for Worker execution in spec/worker_spec.rb
lib/delayed/worker.rb exists but there's no spec/worker_spec.rb visible in the file structure. The worker is the core runtime component that processes queued jobs, so it needs dedicated tests for: job locking, priority ordering, performance under load, and graceful shutdown. This is a high-impact gap for a production-critical component.
- [ ] Create spec/worker_spec.rb file
- [ ] Add tests for processing jobs by priority (verify priority column usage)
- [ ] Add tests for job locking to prevent duplicate execution
- [ ] Add tests for worker shutdown and cleanup
- [ ] Add tests for error handling during job execution
Add tests for MessageSending integration in spec/delayed_method_spec.rb
lib/delayed/message_sending.rb and lib/delayed/performable_method.rb are core components that enable the DSL for delayed method calls, but spec/delayed_method_spec.rb appears to lack coverage for edge cases. The YAML serialization of handler objects is critical—tests should validate: method argument serialization, object state preservation, and deserialization of complex nested objects.
- [ ] Review existing spec/delayed_method_spec.rb test coverage
- [ ] Add tests for serializing methods with complex argument types (hashes, arrays, custom objects)
- [ ] Add tests for method calls on ActiveRecord objects with ID-based loading
- [ ] Add tests for method visibility and private/protected method handling
- [ ] Add regression tests for YAML deserialization edge cases
🌿Good first issues
- Add test coverage for concurrent locking scenario in spec/job_spec.rb—current tests don't verify race conditions when two workers attempt to lock the same job simultaneously
- Document the exact YAML serialization format for custom job objects in README.textile—new users struggle with what fields must be defined on a job class beyond perform()
- Create integration test in spec/ that exercises the full delay.method_name syntax from lib/delayed/message_sending.rb with a realistic Rails model (spec/story_spec.rb hints at this but incomplete)
⭐Top contributors
Click to expand
Top contributors
- [@Tobias Lütke](https://github.com/Tobias Lütke) — 30 commits
- [@Rob Ares](https://github.com/Rob Ares) — 7 commits
- @technoweenie — 6 commits
- @defunkt — 4 commits
- [@Dean Strelau](https://github.com/Dean Strelau) — 3 commits
📝Recent commits
Click to expand
Recent commits
719b628— Merge pull request #29 from IldarManzhikov/master (Tobias Lütke)c4749c3— Merge pull request #32 from kch/master (Tobias Lütke)0cbc532— Merge pull request #31 from BrunoGrasselli/master (Tobias Lütke)50f7f4f— Merge pull request #33 from jamiecobbett/master (Tobias Lütke)7da8b83— Merge pull request #42 from mnemonikk/master (Tobias Lütke)d9165b9— Merge pull request #43 from lorensr/master (Tobias Lütke)01c606e— added multiple worker example (lorensr)a67dc38— make send_later work for module methods (helbig@mediapeers.com)d58bc56— Resolve "invalid byte sequence in US-ASCII (ArgumentError)" (Jamie Cobbett)849a28a— Move rake tasks to lib/tasks; fixes deprecation warning (kch)
🔒Security observations
The delayed_job codebase presents moderate security concerns, primarily centered around YAML deserialization risks (critical in Ruby/Rails) and insufficient input validation on job handlers. The lack of visible authentication/authorization controls and potential SQL injection risks further reduce the security posture. The library's core functionality of executing arbitrary code (via serialized job handlers) requires careful security hardening. The security score is low-to-moderate due to the inherent risks in deserializing untrusted data, compounded by the lack of visible security controls. Immediate attention should be paid to YAML deserialization safety and input validation mechanisms.
- High · YAML Deserialization Risk —
lib/delayed/job.rb, lib/delayed/performable_method.rb, migration template. The delayed_jobs table stores handler data as YAML-encoded strings. YAML deserialization in Ruby can be exploited to execute arbitrary code if untrusted data is deserialized using unsafe methods like YAML.load(). This is a known attack vector in Rails applications. Fix: Use YAML.safe_load() instead of YAML.load() when deserializing handler data. Ensure strict YAML parsing is enforced with permitted classes whitelist. - High · No Input Validation on Job Handler —
lib/delayed/job.rb. The handler field stores serialized objects without apparent validation or sanitization. Malicious actors could inject crafted serialized objects that execute code during deserialization. Fix: Implement strict validation of handler data before storage. Use explicit object whitelisting for deserialization. Consider using JSON instead of YAML for serialization. - Medium · Potential SQL Injection via Query Construction —
lib/delayed/job.rb, lib/delayed/worker.rb. Without reviewing the actual SQL query construction in lib/delayed/job.rb and lib/delayed/worker.rb, there is risk of SQL injection if queries are constructed using string interpolation rather than parameterized queries. Fix: Audit all database queries to ensure parameterized queries are used. Use Rails ActiveRecord methods exclusively and avoid raw SQL where possible. If raw SQL is necessary, always use placeholders (?) with bound parameters. - Medium · Missing Authentication/Authorization Controls —
lib/delayed/job.rb. No evidence of authentication or authorization mechanisms for accessing or modifying delayed jobs. A compromised application server could enqueue arbitrary jobs. Fix: Implement role-based access controls for job creation and management. Validate that only authorized components can enqueue jobs. Log all job-related operations. - Medium · Lack of Dependency Version Pinning —
delayed_job.gemspec. The gemspec file is not provided, making it impossible to verify that dependencies are pinned to specific versions. Unpinned dependencies could introduce vulnerabilities from upstream libraries. Fix: Review the gemspec and ensure all dependencies are pinned to specific versions. Regularly audit dependencies for known vulnerabilities using tools like bundler-audit or Dependabot. - Low · Insufficient Logging and Monitoring —
lib/delayed/worker.rb, lib/delayed/job.rb. Limited evidence of comprehensive logging for job execution, failures, and errors. This could hinder security incident detection and post-incident investigation. Fix: Implement detailed logging of job enqueue/execute/fail events. Log handler information (sanitized), execution time, and errors. Integrate with centralized logging and alerting systems. - Low · Missing Rate Limiting for Job Queue —
lib/delayed/job.rb. No apparent rate limiting or throttling mechanism for job creation. A malicious actor could enqueue unlimited jobs, causing denial of service. Fix: Implement rate limiting per user/source for job enqueueing. Add queue size limits. Implement back-pressure mechanisms to prevent queue flooding.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.