Skip to content

A crash between merge_local and _after_story silently skips done_checkpoint #296

Description

@pbean

Split out of #284 review round 6, finding 2. Not introduced by that PR — it only added one more way to reach it, which is fixed there (86844f6).

The window

WorktreeFlow.integrate_unit calls merge_local(task, unit) for a DONE task. The merge lands and task.unit_merged is persisted, then a tail runs: a unit-merged journal append, the post_merge plugin emit, and the worktree teardown. Any of those can raise — a full disk on the journal write, a plugin raising — and the raise escapes run_unit, _run_story and _loop into run()'s crash handler.

Engine._after_story(task) never fires. On resume, _finish_inflight skips terminal tasks by design, and _after_story is only called inside that loop. In stories mode _after_story is the done_checkpoint pause, so:

  • a mandatory human review is skipped, permanently — no later pass re-offers it
  • the next story dispatches on a resume as if the checkpoint had been honored

The run is visibly crashed, so this is not silent to an operator watching. But the checkpoint is silently gone: nothing in the journal says it was owed and skipped.

Why it isn't closed in #284

The current design relies on "terminal ⇒ _after_story already ran", which holds for every path except a crash inside integrate_unit's tail. Closing it properly needs a durable "post-story hook still owed" marker plus a resume pass that runs and discharges it for terminal tasks — real state and a real lifecycle change, disproportionate inside a PR about a ledger annotation. #284 instead made its own new callsite non-fatal, which removes the raiser it added without touching the general case.

Sketch

  • StoryTask.after_story_done: bool, set (and saved) when _after_story completes or raises RunPaused — the pause is the checkpoint being presented, so resuming past it is correct.
  • _finish_inflight runs _after_story for terminal DONE tasks where the flag is unset, after _reconcile_pending_deferred_closes.
  • Regression: crash inside merge_local's tail (e.g. a raising post_merge plugin) on a story with done_checkpoint: true, then resume — the checkpoint must still pause, and story 2 must not dispatch.

Worth checking at the same time whether any other _after_story-shaped bookkeeping has the same terminal-task blind spot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions