Skip to content

bug(lifecycle): merge-conflict on a PR is detected but never surfaced — no nudge, no notification, no status (suppressed for waiting_input sessions) #295

@neversettle17-101

Description

@neversettle17-101

Bug

PR #278 (ao/agent-orchestrator-2main) is CONFLICTING / DIRTY on GitHub. AO did detect it — the stored PR facts read mergeability: "conflicting":

GitHub:  mergeable=CONFLICTING  mergeStateStatus=DIRTY
AO  /sessions/agent-orchestrator-2/pr → { "mergeability": "conflicting", "ci": "passing", "review": "approved", ... }

But AO never surfaces the conflict: no agent nudge, no user notification, and no attention status/badge. To the user it looks like AO "didn't detect it."

Source: local observation, PR #278 | Reported by: @aditi1178 | Analyzed against: 73649b0
Confidence: High

Reproduction

  1. Worker opens a PR, finishes its turn, and goes waiting_input (needs_input).
  2. The base branch (main) advances so the PR becomes CONFLICTING.
  3. The SCM observer detects it and stores mergeability: conflicting.
  4. No nudge, no notification, no status change — the conflict is invisible unless you open the PR-facts panel and read a single mono row.

Root Cause

AO detects the conflict (data layer is correct), but all three surfacing paths are suppressed:

1. Agent nudge is gated off for waiting_input

backend/internal/lifecycle/reactions.go:65 short-circuits every PR reaction (CI-fail, review-feedback, AND merge-conflict) when the session is paused:

if rec.IsTerminated || rec.Activity.State == domain.ActivityWaitingInput {
    return nil
}

The merge-conflict nudge (reactions.go:90-103, "Your PR has merge conflicts. Rebase onto the base branch and resolve them.") sits below that gate, so for a waiting_input session it never runs. PR #278's session is exactly waiting_input.

2. No user notification type for conflicts

notificationIntentForCurrentSCM (reactions.go:~196-214) only emits PRMerged, PRClosedUnmerged, and ReadyToMerge. There is no Conflicting notification — and ReadyToMerge is itself gated by the same waiting_input check (reactions.go:209). A conflict produces zero notifications.

3. No UI status/badge for conflicts

The frontend status model has no conflicting state:

  • frontend/src/renderer/types/workspace.ts:100 WorkerDisplayStatus = "working" | "needs_you" | "mergeable" | "ci_failed" | "done" — nothing for conflicts.
  • mergeability is rendered only as a buried mono row: frontend/src/renderer/components/SessionInspector.tsx:255 <Row k="Merge" v={prFacts.mergeability || "—"} mono />.
  • Worse: a PR that is approved + passing + conflicting maps to the mergeable / "Ready to merge" attention zone (workspace.ts:199-201) — actively misleading.

Compounding factor

This PR's session is also stuck in waiting_input with a dead runtime pane (see #290), so it can never leave waiting_input; even if the gate allowed the nudge, delivery to the dead pane would fail (see #265). But the surfacing gap is independent: a live waiting_input session with a freshly-conflicting PR is equally silent.

Fix

A merge conflict needs a path to the human even when the agent is paused. Options (not mutually exclusive):

  1. Lift merge-conflict (and CI-fail) above the waiting_input gate in reactions.go, or carve an exception so authoritative PR problems still nudge/notify a waiting_input session.
  2. Add a Conflicting notification type in notificationIntentForCurrentSCM so the user is told regardless of agent state.
  3. Add a conflicting display status to the frontend status model so it lands in the "Needs you" attention zone instead of being invisible / mis-binned as "Ready to merge".

At minimum (1)+(3): surface conflicts as an attention item and stop a conflicting PR from showing as mergeable.

Impact

  • A PR with merge conflicts silently sits as if fine (or even as "Ready to merge") — the user only discovers it by manually opening the PR on GitHub.
  • Defeats the core promise that AO drives PRs to a mergeable state and flags what needs attention.

Related

  • #290 — session stuck in waiting_input with a dead pane (why this session can never leave the state that suppresses the nudge).
  • #265sendOnce blocked by a hung pane (nudge delivery path).
  • #240 — PR surface mapping gaps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglcm-smLifecycle + Session Manager lanepriority: highFix soon

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions