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 pending issues and spawns runPipeline
  • 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

StatusState NameNext StateAction
pendingBacklogBacklogPause gate fires → status→paused. Linear unchanged. Dispatcher will skip it.
pendingTodoIn ProgressDispatcher picks up → full pipeline. onPickedUp → Linear: In Progress.
pendingIn ProgressIn ProgressDispatcher picks up → full pipeline. onPickedUp writes In Progress again.
pendingIn ReviewIn Progress → In ReviewNo gate fires. Dispatcher picks up → full pipeline (entryPoint='full'). onPickedUp sets In Progress, reviewer later sets In Review.
pendingDoneDoneNo gate fires. Dispatcher picks up → full pipeline. Edge case — human manually set Done before pipeline ran.
pending (retry timer active)AnyDispatcher skips (nextRetryAt in future). Wakeup timer fires when elapsed, then re-dispatches. Linear unchanged.
pausedBacklogBacklogPause gate only targets pending — does not fire. Not active — resume gate does not fire. Drift check only. System idle.
pausedTodoIn ProgressResume gate fires → entryPoint='full' → status→pending → dispatcher → full pipeline → onPickedUp → In Progress.
pausedIn ProgressIn ProgressResume gate fires → entryPoint='full' → status→pending → full pipeline.
pausedIn ReviewvariesResume gate fires. Checks review_cycles: no pass verdict → entryPoint='review' (skip dev, run reviewer); pass verdict exists → entryPoint='pr' (skip to PR creation directly).
pausedDoneDoneexpectedLinearStateForReverseSync('paused') = null — no reverse-sync. Drift check only. Human action; system ignores.
runningBacklogIn ReviewPause gate only checks pending — does not interrupt running pipeline. Dev agent completes → onInReview → In Review. Drift recorded in DB.
runningTodoIn ReviewDrift recorded. Pipeline continues uninterrupted. Dev completes → reviewer starts → In Review.
runningIn ProgressIn ReviewNormal. onPickedUp already set this. Dev completes → onInReview → In Review.
runningIn ReviewIn ReviewDrift (Linear jumped ahead). Pipeline continues. Dev completes → reviewer → In Review already matches.
runningDoneIn ReviewDrift. Pipeline continues. Dev completes → onInReview overwrites to In Review.
reviewingIn ReviewIn Review or In ProgressNormal. Pass → PR opened, Linear stays In Review. Fail → onReviewFailed → In Progress, dev reruns.
reviewingIn ProgressIn ReviewDrift (Linear fell back). Reviewer continues. Pass → In Review. Fail → onReviewFailed writes In Progress again.
reviewingBacklogIn ReviewDrift. Reverse-sync returns null for reviewing. Reviewer continues. Completion writes correct state.
reviewingTodoIn ReviewDrift. Reviewer continues uninterrupted. Same as Backlog drift.
reviewingDoneIn ReviewDrift. Reviewer continues. onReadyToMerge or onFlagged sets In Review on completion.
pr_openIn ReviewIn Review → DoneNormal. GitHub reconcile or merge webhook detects merge → applyMergedPr → Linear: Done, status→merged.
pr_openBacklogIn ReviewReverse-sync fires — expected=In Review ≠ Backlog → Linear corrected to In Review.
pr_openTodoIn ReviewReverse-sync fires — corrected to In Review.
pr_openIn ProgressIn ReviewReverse-sync fires — corrected to In Review.
pr_openDoneIn Review (brief) → DoneGitHub 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.
mergedDoneDoneTerminal. Reverse-sync: expected=Done, matches. No action.
mergedBacklogDoneReverse-sync fires — corrected to Done.
mergedTodoDoneReverse-sync fires — corrected to Done.
mergedIn ProgressDoneReverse-sync fires — corrected to Done.
mergedIn ReviewDoneReverse-sync fires — corrected to Done.
failedBacklogBacklogNormal. onFailed already set Backlog. Reverse-sync: expected=Backlog, matches. Idle — waiting for human to move ticket.
failedTodoIn ProgressResume gate fires → entryPoint='full' → status→pending → full pipeline restart → onPickedUp → In Progress.
failedIn ProgressIn ProgressResume gate fires → entryPoint='full' → full pipeline restart. onPickedUp writes In Progress.
failedIn ReviewvariesResume gate fires. Checks review_cycles: pass verdictentryPoint='pr' → skip dev+review, jump to PR creation, Linear stays In Review. No pass verdictentryPoint='review' → skip dev, re-run reviewer. No cyclesentryPoint='review' → reviewer runs on existing worktree.
failedDoneBacklogReverse-sync fires — expected=Backlog ≠ Done → Linear corrected to Backlog.

Special Cases

ScenarioAction
Circular dependency detectedAll 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.