Install
$ agentstack add skill-nimadorostkar-claude-skills-collection-background-jobs ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Background Jobs
Purpose
Move work out of the request path without losing it. A job system is a distributed system with a friendly interface; the failure modes are the same, and they are hidden until they are not.
When to Use
- Work that is slow, retriable, or must survive a request timeout: emails, exports, webhooks, media processing.
- Scheduled and recurring work.
- Diagnosing duplicate side effects, stuck queues, or jobs that silently disappear.
Capabilities
- Queue and worker design, including priority and isolation.
- Idempotency and exactly-once effects.
- Retry policy: backoff, jitter, attempt limits, dead-letter handling.
- Scheduling: cron jobs, delayed jobs, and deduplicating them across instances.
- Observability: queue depth, age, failure rate.
Inputs
- The work to be done and whether it can safely run twice.
- Latency expectations: seconds, minutes, or overnight.
- Volume, and how bursty it is.
Outputs
- Job handlers that are idempotent by construction.
- Explicit retry and DLQ policies per job type.
- Alerts on queue depth and oldest-message age.
Workflow
- Make it idempotent first — Before anything else. A job will run twice; design so that this is harmless. Key the side effect, not the invocation.
- Pass identifiers, not objects — Enqueue
{"order_id": "..."}, not a serialized order. By the time the job runs, the object may be stale. - Set the retry policy per job — A transient network failure deserves exponential backoff and ten attempts. A validation failure deserves zero retries and a DLQ.
- Isolate the queues — A flood of low-priority image resizes must not delay password-reset emails. Separate queues, separate workers.
- Deduplicate scheduled work — With multiple instances, every instance's scheduler fires. Use a lock keyed on the job name and window.
- Alert on age, not just depth — A queue with 10 messages that are 3 hours old is a worse signal than a queue with 10,000 that are 5 seconds old.
Best Practices
- Every job handler must tolerate being called twice with the same arguments. This is the single rule that prevents most job-system incidents.
- Never enqueue inside a database transaction that has not committed. The worker will pick the job up and find no row. Use the outbox pattern or an after-commit hook.
- A job with no timeout will eventually hang and hold a worker forever. Set one.
- Retry only what is retriable. Retrying a 400 from an upstream API twenty times is a way to get rate-limited for a permanent failure.
- Log the job ID, the attempt number, and the argument identifiers on every execution. Without this, debugging is guesswork.
- Keep handlers small. A job that does five things fails partway through the third.
Examples
Idempotent handler with a natural key:
@job(queue="billing", max_attempts=5, backoff="exponential", timeout=30)
def send_invoice(invoice_id: str) -> None:
invoice = Invoice.get(invoice_id)
# The natural idempotency key: an invoice is sent at most once.
if invoice.sent_at is not None:
logger.info("invoice_already_sent", extra={"invoice_id": invoice_id})
return
message_id = mailer.send(
to=invoice.customer_email,
template="invoice",
context=invoice.render_context(),
idempotency_key=f"invoice:{invoice_id}", # the provider dedupes too
)
invoice.update(sent_at=utcnow(), provider_message_id=message_id)
Two protections, deliberately: the local check handles the common case, and the provider's idempotency key handles the crash between send and update.
Enqueue after commit, not inside the transaction:
with db.transaction():
order = Order.create(...)
db.after_commit(lambda: send_invoice.enqueue(order.invoice_id))
Notes
- The most common job-system bug is enqueueing from inside an uncommitted transaction. The worker is fast enough to look for a row that does not exist yet, and the job fails with "not found" only under load.
- Exponential backoff without jitter causes retry storms to synchronize. Always add jitter.
- Long-running jobs should checkpoint. A 40-minute export that fails at minute 39 and restarts from zero will never complete under any retry policy.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: nimadorostkar
- Source: nimadorostkar/Claude-Skills-collection
- License: MIT
- Homepage: https://github.com/nimadorostkar/Claude-Skills-collection
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.