Task Lifecycle¶
A scan task is the unit of work: one scan of one repository at one ref. It moves through a small, enforced state machine.
States¶
| Status | Meaning |
|---|---|
pending |
just created, not yet published to the queue |
queued |
published to NATS, waiting for a worker |
running |
a worker claimed it and is scanning |
completed |
finished, an analysis_id is set |
failed |
a worker ran it and it errored |
expired |
was running too long and the reconciler gave up on it |
completed, failed, and expired are terminal for the worker — but failed and expired can be retried by the reconciler.
Transitions¶
┌──────────────┐
│ pending │
└──────┬───────┘
│ schedule (publish job)
▼
┌──────────────┐ claim ┌──────────────┐
│ queued │──────────────────▶│ running │
└──────┬───────┘ └──────┬───────┘
│ │
stale queued (no worker) │
│ complete │ fail
▼ ▼
re-publish to queue ┌────────────┐┌────────┐
│ completed ││ failed │
└────────────┘└───┬────┘
│
stale running (max_stale_min)
▼
┌────────┐
│expired │
└────────┘
Transitions are validated in the domain (internal/core/domain/scantasks); an illegal move returns ErrInvalidTransition.
- A task can only be claimed if it is
pending/queuedandattempt < max_attempts(defaultmax_attemptsis 3). - Claiming bumps
attemptby one and setsrunning. running→completed(withanalysis_id),failed(with an error), orexpired(with a reason).
Retries¶
- Each task carries
attempt/max_attempts(default 3). - A worker that fails a scan marks it
failed; a task the reconciler expires isexpired. - The reconciler's
retryDeadstep re-queues anyfailed/expiredtask that still has attempts left (attempt < max_attempts), then re-publishes the job to NATS. - The NATS consumer is configured with
MaxDeliver: 3, so a poison message is also bounded at the transport layer.
The reconciler¶
Started by serve, the reconciler ticks every 30 seconds and runs three passes:
reconcileStale— findrunningtasks that have been stale (no progress) longer thanmax_stale_minandExpirethem with reasonstale: worker disconnected.retryDead— re-queue and re-publishfailed/expiredtasks that still have attempts left.rescheduleStaleQueued— re-publishqueuedtasks that have sat untouched longer thanqueue_stale_min(covers a job lost in the queue).
Each pass broadcasts a status event so SSE subscribers and the UI update live.
Note — while a scan is running, the worker also heartbeats the task's
updated_at(well belowmax_stale_min) so a legitimately long scan is not mistaken for a dead worker.
Related¶
- Task Lifecycle flags —
max_stale_min,queue_stale_min. - HTTP API — read task state and results.