--- 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.