Skip to content

bug(lifecycle/scm): merge-triggered termination races the still-working agent — PRs opened after termination are never attributed or enriched #2879

Description

@AgentWrapper

Bug

When a worker's first PR merges, the lifecycle completion reaction terminates the session (sessionComplete: ≥1 merged, none open → MarkTerminated). But because merge-termination is flag-only (see #2811), the agent process is still alive and still working. Any PR it opens after that instant is invisible: the SCM observer does not observe terminated sessions, so the new PR is never attributed to the session, never enriched (no sidebar entry, no checks/review tracking, no nudges), and no lifecycle reaction will ever fire for it.

Source: maintainer local report (desktop app, macOS) | Date: 2026-07-21 | Analyzed against: dec77c61b | Daemon: app-owned, :3001
Confidence: High — timeline reconstructed from change_log + GitHub timestamps
Priority: high — silent loss of PR tracking for any multi-PR worker whose first PR merges quickly (fast-merge workflows make this common)

Live repro (session agent-orchestrator-34, codex worker)

Time (UTC, 2026-07-21) Event
07:09:48 Agent opens PR #2876 (branch ao/agent-orchestrator-34/landing-docs-trailing-slash)
07:10:12 Observer attributes it — pr_created in change_log ✓ (enrichment works, including on a non-root branch)
07:11:47 #2876 merges
07:11:49 Session terminated by the merged-PR completion reaction (change_log: isTerminated: true)
08:11:51 Agent — still alive in its tmux session, an hour into further work — opens PR #2877 (branch ao/agent-orchestrator-34/landing-orchestrator-cta)
now pr table has no row for #2877; sidebar shows nothing; PR is fully untracked

Root Cause

Two interacting defects:

  1. Premature completion inference. sessionComplete (backend/internal/lifecycle/reactions.go:277-292) infers "the session is done" purely from PR state (≥1 merged ∧ 0 open). That inference races the agent: an agent mid-task with one merged PR and the next branch not yet pushed is not done, but the reaction terminates the session anyway (reactions.go:152-157MarkTerminated).
  2. Observation cutoff on termination. The SCM observer skips terminated sessions, so any PR created after the termination instant is never discovered. Combined with #2811's flag-only termination (agent left running), the system actively manufactures this state: it terminates the bookkeeping while leaving the worker alive to keep producing untracked work.

Fix

Suggested directions:

  1. Gate completion on agent state, not just PR state: only auto-terminate when the session is also quiescent (activity exited/idle for a grace window) — an agent that is active with one merged PR should stay alive and observed.
  2. Grace period / late-PR sweep: after merge-triggered termination, keep observing the session's branch namespace (ao/<session-id>/*) for N minutes; if a new PR appears, revive attribution (or un-terminate the session).
  3. Attribution fallback: on any observer scan, attribute an unowned open PR whose head branch matches ao/<session-id>/* to that session even if terminated, and surface it (at minimum ao session claim-pr should work on terminated sessions so a human can recover).

Impact

  • Agent work product silently vanishes from AO's tracking: no sidebar presence, no CI/review routing, no nudges — humans discover the PR only by luck on GitHub.
  • Compounds #2811: the leaked-alive agent keeps working and its output is systematically dropped.

Related

  • #2811: flag-only merge termination (enables the race); fix PRs in flight #2819/#2840/#2853 — whichever lands should consider this issue's completion-gating too
  • #2390: terminated-session status derivation with open PRs (adjacent status bug)
  • #1821 (closed): multi-PR support — this is a lifecycle-era regression of the same spirit
  • #2834: merged sessions disappearing from sidebar

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions