Problem
Multi-file lifecycle commands (rk close, rk start, rk cancel, rk reopen) apply mutations as sequential await calls with no journal. A crash or SIGKILL between writes leaves the project in a partially-mutated state.
rk close mutation sequence (highest risk):
| Step |
File |
Change |
| 1 |
sprints/S-NNN.md |
status: shipped, closed_at, end_sha |
| 2 |
reviews/R-NNN.md |
end_sha backfill (optional) |
| 3 |
queues/<lane>.md |
remove slot |
| 4 |
.repokernel/registry.json |
refreshRegistry |
A crash after step 1 leaves status: shipped but the slot still present in the queue — detectable via SHIPPED_SPRINT_IN_QUEUE and fixable via rk fix --apply. A crash between steps 1 and 3 during rk start (sprint becomes active before queue updates) has no automated repair path.
Current mitigations
rk fix --apply repairs SHIPPED_SPRINT_IN_QUEUE and CANCELLED_SPRINT_IN_QUEUE
rk registry --write regenerates a stale registry
- Re-running commands is partially idempotent
Proposed solution: transaction journal
Write a .git/repokernel/journal/<op-id>.pending.json file before the first mutation, with each step's completedAt updated atomically after it completes. A matching <op-id>.done sentinel is written after the final step.
On rk recover --apply, scan for .pending.json files without a .done sentinel and either forward-complete or roll back the partial operation.
See docs/internals/crash-recovery.md for full design: journal schema, recovery protocol, options table, and phased implementation plan.
Implementation phases
- 2a Journal write for
rk close
- 2b Journal write for
rk start, rk cancel, rk reopen
- 2c
rk recover --apply forward-completion path
- 2d Rollback support
Related
DUPLICATE_REVIEW_ID handler in fix.ts (lines 637–659) is dead code — loadProject short-circuits before runValidators when duplicate IDs exist. Should be removed or the graph-phase error handling refactored to allow fix.ts to see the finding. Track separately.
Problem
Multi-file lifecycle commands (
rk close,rk start,rk cancel,rk reopen) apply mutations as sequentialawaitcalls with no journal. A crash orSIGKILLbetween writes leaves the project in a partially-mutated state.rk closemutation sequence (highest risk):sprints/S-NNN.mdstatus: shipped,closed_at,end_shareviews/R-NNN.mdend_shabackfill (optional)queues/<lane>.md.repokernel/registry.jsonA crash after step 1 leaves
status: shippedbut the slot still present in the queue — detectable viaSHIPPED_SPRINT_IN_QUEUEand fixable viark fix --apply. A crash between steps 1 and 3 duringrk start(sprint becomesactivebefore queue updates) has no automated repair path.Current mitigations
rk fix --applyrepairsSHIPPED_SPRINT_IN_QUEUEandCANCELLED_SPRINT_IN_QUEUErk registry --writeregenerates a stale registryProposed solution: transaction journal
Write a
.git/repokernel/journal/<op-id>.pending.jsonfile before the first mutation, with each step'scompletedAtupdated atomically after it completes. A matching<op-id>.donesentinel is written after the final step.On
rk recover --apply, scan for.pending.jsonfiles without a.donesentinel and either forward-complete or roll back the partial operation.See
docs/internals/crash-recovery.mdfor full design: journal schema, recovery protocol, options table, and phased implementation plan.Implementation phases
rk closerk start,rk cancel,rk reopenrk recover --applyforward-completion pathRelated
DUPLICATE_REVIEW_IDhandler infix.ts(lines 637–659) is dead code —loadProjectshort-circuits beforerunValidatorswhen duplicate IDs exist. Should be removed or the graph-phase error handling refactored to allow fix.ts to see the finding. Track separately.