Pipeline State Mapper
Columns
- Status — internal pipeline status (SQLite)
- State Name — Linear board state at the time of reconcile
- Next State — Linear state after the action completes
- Action — what the system does
Mechanisms
- Reconcile tick — runs every N seconds, reads all Linear issues, fires gates
- Dispatcher — picks up
pendingissues and spawnsrunPipeline - Pipeline — internal stages: dev agent → review loop → PR creation
- Reverse-sync — reconciler corrects Linear state drift for terminal statuses back to their expected value
Main Grid
| Status | State Name | Next State | Action |
|---|---|---|---|
pending | Backlog | Backlog | Pause gate fires → status→paused. Linear unchanged. Dispatcher will skip it. |
pending | Todo | In Progress | Dispatcher picks up → full pipeline. onPickedUp → Linear: In Progress. |
pending | In Progress | In Progress | Dispatcher picks up → full pipeline. onPickedUp writes In Progress again. |
pending | In Review | In Progress → In Review | No gate fires. Dispatcher picks up → full pipeline (entryPoint='full'). onPickedUp sets In Progress, reviewer later sets In Review. |
pending | Done | Done | No gate fires. Dispatcher picks up → full pipeline. Edge case — human manually set Done before pipeline ran. |
pending (retry timer active) | Any | — | Dispatcher skips (nextRetryAt in future). Wakeup timer fires when elapsed, then re-dispatches. Linear unchanged. |
paused | Backlog | Backlog | Pause gate only targets pending — does not fire. Not active — resume gate does not fire. Drift check only. System idle. |
paused | Todo | In Progress | Resume gate fires → entryPoint='full' → status→pending → dispatcher → full pipeline → onPickedUp → In Progress. |
paused | In Progress | In Progress | Resume gate fires → entryPoint='full' → status→pending → full pipeline. |
paused | In Review | varies | Resume gate fires. Checks review_cycles: no pass verdict → entryPoint='review' (skip dev, run reviewer); pass verdict exists → entryPoint='pr' (skip to PR creation directly). |
paused | Done | Done | expectedLinearStateForReverseSync('paused') = null — no reverse-sync. Drift check only. Human action; system ignores. |
running | Backlog | In Review | Pause gate only checks pending — does not interrupt running pipeline. Dev agent completes → onInReview → In Review. Drift recorded in DB. |
running | Todo | In Review | Drift recorded. Pipeline continues uninterrupted. Dev completes → reviewer starts → In Review. |
running | In Progress | In Review | Normal. onPickedUp already set this. Dev completes → onInReview → In Review. |
running | In Review | In Review | Drift (Linear jumped ahead). Pipeline continues. Dev completes → reviewer → In Review already matches. |
running | Done | In Review | Drift. Pipeline continues. Dev completes → onInReview overwrites to In Review. |
reviewing | In Review | In Review or In Progress | Normal. Pass → PR opened, Linear stays In Review. Fail → onReviewFailed → In Progress, dev reruns. |
reviewing | In Progress | In Review | Drift (Linear fell back). Reviewer continues. Pass → In Review. Fail → onReviewFailed writes In Progress again. |
reviewing | Backlog | In Review | Drift. Reverse-sync returns null for reviewing. Reviewer continues. Completion writes correct state. |
reviewing | Todo | In Review | Drift. Reviewer continues uninterrupted. Same as Backlog drift. |
reviewing | Done | In Review | Drift. Reviewer continues. onReadyToMerge or onFlagged sets In Review on completion. |
pr_open | In Review | In Review → Done | Normal. GitHub reconcile or merge webhook detects merge → applyMergedPr → Linear: Done, status→merged. |
pr_open | Backlog | In Review | Reverse-sync fires — expected=In Review ≠ Backlog → Linear corrected to In Review. |
pr_open | Todo | In Review | Reverse-sync fires — corrected to In Review. |
pr_open | In Progress | In Review | Reverse-sync fires — corrected to In Review. |
pr_open | Done | In Review (brief) → Done | GitHub merge fires first in practice → status flips to merged. If still pr_open when reconcile ticks: reverse-sync corrects to In Review briefly until merge is detected. |
merged | Done | Done | Terminal. Reverse-sync: expected=Done, matches. No action. |
merged | Backlog | Done | Reverse-sync fires — corrected to Done. |
merged | Todo | Done | Reverse-sync fires — corrected to Done. |
merged | In Progress | Done | Reverse-sync fires — corrected to Done. |
merged | In Review | Done | Reverse-sync fires — corrected to Done. |
failed | Backlog | Backlog | Normal. onFailed already set Backlog. Reverse-sync: expected=Backlog, matches. Idle — waiting for human to move ticket. |
failed | Todo | In Progress | Resume gate fires → entryPoint='full' → status→pending → full pipeline restart → onPickedUp → In Progress. |
failed | In Progress | In Progress | Resume gate fires → entryPoint='full' → full pipeline restart. onPickedUp writes In Progress. |
failed | In Review | varies | Resume gate fires. Checks review_cycles: pass verdict → entryPoint='pr' → skip dev+review, jump to PR creation, Linear stays In Review. No pass verdict → entryPoint='review' → skip dev, re-run reviewer. No cycles → entryPoint='review' → reviewer runs on existing worktree. |
failed | Done | Backlog | Reverse-sync fires — expected=Backlog ≠ Done → Linear corrected to Backlog. |
Special Cases
| Scenario | Action |
|---|---|
| Circular dependency detected | All cycle members → failed, onFailed → Linear: Backlog, Slack alert fired. Unresolved external deps are skipped silently until the blocking ticket is ingested. |
PR closed without merge (pr_open) | GitHub reconcile detects closed PR → status→paused, onBacklog → Linear: Backlog. Human retriage required — move to Todo to restart. |
| Transient failure (rate_limit / timeout / network / schema_invalid within cap) | Status stays pending, nextRetryAt set, worktree kept. Dispatcher skips until timer fires. Linear unchanged. Backoff: rate_limit→5 min, schema_invalid→30 s, timeout/network→1 min / 5 min / 15 min. |
| Permanent failure (agent_error, or transient cap exhausted) | Status→failed, worktree dropped, onFailed → Linear: Backlog, Slack notified. |
| Dependent ticket unblocked (blocker merges) | dispatch("pipeline_complete") fires → blocked dependent moves from pending to dispatched → onPickedUp → Linear: In Progress. |