First: is everything running?
The campaign will not start
Stuck in syncing
The CSV sync runs as an ARQ job. If it never completes:
running but no calls go out
Work through in order:
1. Are there queued runs left?
PhoneNumberPoolExhaustedError in the logs means no attachable number was free. The dispatcher retries up to 3 times before giving up.
3. Are concurrency slots free? If the organisation is already at its ceiling, the dispatcher waits up to 120 seconds and then raises ConcurrentSlotAcquisitionError.
campaign_events channel. If its logs are silent while batches complete, it is not receiving events — restart it.
The campaign paused itself
The circuit breaker tripped. It watches a rolling window and pauses the campaign when the failure rate crosses the threshold, so a broken agent cannot burn the whole list. Defaults (apps/api/app/constants/campaign.py):
Find out why the calls failed before resuming:
The campaign is failed and will not resume
failed is terminal. Resume is only permitted from paused, so a campaign that failed cannot be restarted through the API even when queued runs remain. Redial is the only recovery path.A single batch exception is enough to reach this state — including a ConcurrentSlotAcquisitionError from a 120-second slot shortage under load, which fails the whole campaign rather than just the batch.Completed too early
If the orchestrator restarted mid-run, completion detection falls back to document timestamps. A campaign idle for more than an hour (completion_timeout = 3600) with no pending work is marked completed on the first sweep — even if a long-delay retry would otherwise have continued it.
Check progress for unprocessed contacts, and redial if any remain.
Calls dialled twice
You are running more than one campaign orchestrator.Retries do not happen as expected
Defaults:max_retries: 2, retry_delay_seconds: 120, retry on busy and no_answer, not on voicemail.
Two asymmetries surprise people:
cancelledcounts as a failure for the circuit breaker but is never retried.failedis retried, but has no per-reason toggle — onlybusy,no_answer, andvoicemailcan be individually disabled.
QueuedRuns document rather than mutating the original; the unique index on (campaign_id, source_uuid, retry_count) makes that idempotent.