---
title: "Statuses & Transitions"
status: active
audience: [contributor]
language: en
last_updated: 2026-05-07
when_to_read: "Whenever an entity's status changes unexpectedly or you need to know what status comes next."
---

# 02 — Statuses & Transitions

Every persistent entity in Scrum4Me has an explicit status enum. This chapter documents them all, with state-machine diagrams showing allowed transitions, the trigger for each transition (user action vs system / job-driven), and the side effects.

> **Hardstop:** the database stores enums in `UPPER_SNAKE`; the REST API exposes them in `lowercase`. Conversion happens **only** through [`lib/task-status.ts`](../../lib/task-status.ts) — never call `.toLowerCase()` or `.toUpperCase()` directly. See the [DB vs API mapping](#db-vs-api-mapping) section at the end.

## Quick reference

| Entity | Source enum | Statuses |
|---|---|---|
| [PBI](#pbi) | `PbiStatus` | `READY`, `BLOCKED`, `DONE`, `FAILED` |
| [Story](#story) | `StoryStatus` | `OPEN`, `IN_SPRINT`, `DONE`, `FAILED` |
| [Task](#task) | `TaskStatus` | `TO_DO`, `IN_PROGRESS`, `REVIEW`, `DONE`, `FAILED` |
| [Sprint](#sprint) | `SprintStatus` | `ACTIVE`, `COMPLETED`, `FAILED` |
| [SprintRun](#sprintrun) | `SprintRunStatus` | `QUEUED`, `RUNNING`, `PAUSED`, `DONE`, `FAILED`, `CANCELLED` |
| [ClaudeJob](#claudejob) | `ClaudeJobStatus` | `QUEUED`, `CLAIMED`, `RUNNING`, `DONE`, `FAILED`, `CANCELLED`, `SKIPPED` |
| [Idea](#idea) | `IdeaStatus` | `DRAFT`, `GRILLING`, `GRILL_FAILED`, `GRILLED`, `PLANNING`, `PLAN_FAILED`, `PLAN_READY`, `PLANNED` |

## PBI

A **Product Backlog Item** holds one or more stories. Its status reflects whether the PBI as a whole is ready to be picked up, blocked on something external, finished, or written off.

```mermaid
stateDiagram-v2
    [*] --> READY: create_pbi
    READY --> BLOCKED: user marks blocked
    BLOCKED --> READY: user unblocks
    READY --> DONE: all stories DONE
    READY --> FAILED: user gives up
    BLOCKED --> FAILED: user gives up
    DONE --> [*]
    FAILED --> [*]
```

| Transition | Trigger | Side effect |
|---|---|---|
| `* → READY` | `create_pbi` MCP tool or PBI dialog | New PBI lands in `priority` group, `sort_order = last + 1` |
| `READY ↔ BLOCKED` | User toggles via PBI dialog | None besides log entry |
| `READY → DONE` | All child stories reach `DONE` | Auto-promotion (see [ST-1109 plan](../plans/ST-1109-pbi-status.md)) |
| `* → FAILED` | User gives up on the PBI | Stories may remain `OPEN`; PBI is filtered out of active boards |

## Story

A **Story** sits under a PBI. It moves out of the backlog when added to a Sprint, and reaches `DONE` when its tasks are complete and the implementation is verified.

```mermaid
stateDiagram-v2
    [*] --> OPEN: create_story
    OPEN --> IN_SPRINT: added to sprint
    IN_SPRINT --> OPEN: removed from sprint
    IN_SPRINT --> DONE: all tasks DONE + verify passes
    IN_SPRINT --> FAILED: verify fails / abandoned
    DONE --> [*]
    FAILED --> [*]
```

| Transition | Trigger | Side effect |
|---|---|---|
| `* → OPEN` | `create_story` MCP tool or Story dialog | Lives in product backlog |
| `OPEN ↔ IN_SPRINT` | Drag onto Sprint board, or sprint-removal | Tasks denormalise `sprint_id` |
| `IN_SPRINT → DONE` | Story completion via MCP / UI; auto-PR flow may trigger | Auto-PR flow ([`runbooks/auto-pr-flow.md`](../runbooks/auto-pr-flow.md)) may run; PBI is re-evaluated for `READY → DONE` |
| `IN_SPRINT → FAILED` | Verification failure or manual abandon | Logged in story log |

## Task

A **Task** is the smallest unit. The Claude worker mainly reads `implementation_plan` and writes status transitions through MCP tools.

```mermaid
stateDiagram-v2
    [*] --> TO_DO: create_task
    TO_DO --> IN_PROGRESS: agent claims / user starts
    IN_PROGRESS --> REVIEW: implementation done, awaiting verify
    REVIEW --> DONE: verify passes
    REVIEW --> IN_PROGRESS: verify fails, retry
    IN_PROGRESS --> FAILED: unrecoverable error
    REVIEW --> FAILED: gives up after retries
    DONE --> [*]
    FAILED --> [*]
```

| Transition | Trigger | Side effect |
|---|---|---|
| `* → TO_DO` | `create_task` MCP tool / Task dialog | Inherits `sprint_id` from parent story |
| `TO_DO → IN_PROGRESS` | Worker claim or user starts | Story may auto-promote to `IN_SPRINT` |
| `IN_PROGRESS → REVIEW` | Implementation logged | Optional `verify_task_against_plan` runs |
| `REVIEW → DONE` | Verify passes / human accepts | When all sibling tasks are `DONE`, the parent story is eligible for `DONE` |
| `* → FAILED` | Unrecoverable error or human marks failed | Story may auto-promote to `FAILED` |

The MCP tool is `update_task_status({ task_id, status })` accepting lowercase API values: `todo | in_progress | review | done | failed`.

## Sprint

A **Sprint** is the cross-cutting time-box. Its status tracks the overall sprint container, not the agent execution.

```mermaid
stateDiagram-v2
    [*] --> ACTIVE: create sprint
    ACTIVE --> COMPLETED: user closes sprint
    ACTIVE --> FAILED: user abandons sprint
    COMPLETED --> [*]
    FAILED --> [*]
```

For execution semantics (PER_TASK vs SPRINT_BATCH) see [`docs/architecture/sprint-execution-modes.md`](../architecture/sprint-execution-modes.md).

## SprintRun

A **SprintRun** is one execution attempt of a sprint by the agent worker. Multiple runs may exist over a sprint's lifetime (if a run is cancelled or paused and restarted).

```mermaid
stateDiagram-v2
    [*] --> QUEUED: trigger sprint run
    QUEUED --> RUNNING: worker claims
    RUNNING --> PAUSED: pause requested
    PAUSED --> RUNNING: resume
    RUNNING --> DONE: all tasks done
    RUNNING --> FAILED: unrecoverable
    QUEUED --> CANCELLED: user cancels
    RUNNING --> CANCELLED: user cancels
    PAUSED --> CANCELLED: user cancels
    DONE --> [*]
    FAILED --> [*]
    CANCELLED --> [*]
```

The cascade rules (which task transitions automatically promote the SprintRun) are described in [`docs/plans/sprint-pr-worktree-state-machines.md`](../plans/sprint-pr-worktree-state-machines.md). When calling `update_task_status` from inside a sprint run, pass the optional `sprint_run_id` so the server can validate ownership and propagate cascades.

## ClaudeJob

The agent **job queue** (M13). Each enqueued unit of work is a `ClaudeJob` with a `kind` (`TASK_IMPLEMENTATION`, `IDEA_GRILL`, `IDEA_MAKE_PLAN`, `PLAN_CHAT`, `SPRINT_IMPLEMENTATION`).

```mermaid
stateDiagram-v2
    [*] --> QUEUED: enqueue
    QUEUED --> CLAIMED: wait_for_job (FOR UPDATE SKIP LOCKED)
    CLAIMED --> RUNNING: worker starts
    RUNNING --> DONE: update_job_status('done')
    RUNNING --> FAILED: update_job_status('failed')
    QUEUED --> CANCELLED: user cancels
    CLAIMED --> QUEUED: stale (>30min)
    QUEUED --> SKIPPED: superseded
    DONE --> [*]
    FAILED --> [*]
    CANCELLED --> [*]
    SKIPPED --> [*]
```

| Transition | Trigger | Side effect |
|---|---|---|
| `QUEUED → CLAIMED` | `wait_for_job` atomically claims | Bearer token is bound to the job (`claimed_by_token_id`) |
| `CLAIMED → QUEUED` | Stale claim (>30 min) | Auto-requeue on next `wait_for_job` |
| `RUNNING → DONE` | `update_job_status('done')` | Optional token-cost telemetry stored on the row |
| `RUNNING → FAILED` | `update_job_status('failed')` | For `IDEA_GRILL`/`IDEA_MAKE_PLAN`, idea status auto-rolls to `GRILL_FAILED` / `PLAN_FAILED` |

For idempotency rules and recovery procedures see [`docs/runbooks/worker-idempotency.md`](../runbooks/worker-idempotency.md).

## Idea

The **Idea** entity (M12) is a pre-PBI staging area. It goes through two AI-driven phases: a **grill** (Q&A loop with the user to clarify the idea) and a **plan** (single-pass output of a structured PBI tree). Failures are explicit terminal-ish states that allow retry.

```mermaid
stateDiagram-v2
    [*] --> DRAFT: create idea
    DRAFT --> GRILLING: enqueue IDEA_GRILL
    GRILLING --> GRILLED: update_idea_grill_md
    GRILLING --> GRILL_FAILED: job failed
    GRILL_FAILED --> GRILLING: retry
    GRILLED --> PLANNING: enqueue IDEA_MAKE_PLAN
    PLANNING --> PLAN_READY: update_idea_plan_md (parse ok)
    PLANNING --> PLAN_FAILED: parsePlanMd rejected
    PLAN_FAILED --> PLANNING: retry
    PLAN_READY --> PLANNED: PBI tree created
    PLANNED --> [*]
```

| Transition | Trigger | Side effect |
|---|---|---|
| `DRAFT → GRILLING` | User clicks "Grill" | Enqueues `IDEA_GRILL` job; worker reads `prompt_text` + `idea.grill_md` |
| `GRILLING → GRILLED` | `update_idea_grill_md` | Logs `IdeaLog{GRILL_RESULT}` |
| `* → GRILL_FAILED` | `update_job_status('failed')` for `IDEA_GRILL` | Idea remains usable; user can retry |
| `GRILLED → PLANNING` | User clicks "Make plan" | Enqueues `IDEA_MAKE_PLAN`; worker outputs strict YAML-frontmatter |
| `PLANNING → PLAN_READY` | `update_idea_plan_md` parse ok | Logs `IdeaLog{PLAN_RESULT}` |
| `PLANNING → PLAN_FAILED` | `parsePlanMd` rejected | Logs `IdeaLog{JOB_EVENT, errors}` |
| `PLAN_READY → PLANNED` | PBI tree generated from plan | Idea is archived; PBI/Story/Task tree appears in the backlog |

For the full Idea workflow, prompts, and `prompt_text` contents, see [`docs/plans/M12-ideas.md`](../plans/M12-ideas.md).

## DB vs API mapping

> **Hardstop:** never bypass [`lib/task-status.ts`](../../lib/task-status.ts).

The database stores enums in `UPPER_SNAKE` (`TO_DO`, `IN_PROGRESS`, `IN_SPRINT`, …) because Prisma + PostgreSQL prefer that convention. The REST API exposes them in `lowercase` (`todo`, `in_progress`, `in_sprint`, …) because that's the convention HTTP consumers expect.

The two are mapped **only** through the helpers in [`lib/task-status.ts`](../../lib/task-status.ts):

```ts
taskStatusToApi(status)      // DB → API
taskStatusFromApi(input)     // API → DB (returns null on bad input)
storyStatusToApi(status)
storyStatusFromApi(input)
pbiStatusToApi(status)
pbiStatusFromApi(input)
sprintStatusToApi(status)
sprintStatusFromApi(input)
sprintRunStatusToApi(status)
sprintRunStatusFromApi(input)
```

Bad input on the inbound side (`*FromApi`) returns `null` — the route handler converts that to a `422` Zod-style error. See [`docs/adr/0004-status-enum-mapping.md`](../adr/0004-status-enum-mapping.md) for the rationale.

## What's next

→ [03 — Git Workflow](./03-git-workflow.md) covers branching, commits, and the cost-driven PR rules.
