Skip to content

Agent switch gets stuck when duplicate retirement notifications clear pending preflight #882

Description

@DDKinger

Problem

Switching the agent pane from Copilot to Claude through Settings can leave the pane stuck at Starting agent... / unable to connect. The old ACP connection retires, but the replacement connection is never started because a valid preflight result is discarded as stale.

Observed on Intelligent Terminal Dev 0.8.0.2, Windows, with WTA built from 602220e7957c1e53d48d0fbab8bb775a1b2feac9. The implicated state handling is also present on main at efd10cfb20072d715d20d023cb141df17b8324d1.

Reproduction

  1. Open an agent pane and connect to Copilot.
  2. In Settings, change the agent to Claude while retaining the existing helper/pane.
  3. The old ACP connection closes and target preflight starts.
  4. The helper receives both AgentTransportRetired and AgentReconnectReady for the rebind before preflight completes.
  5. The pane remains in its connecting state. Master receives no replacement Claude initialize request for this operation.

Expected: Rebind completion notifications should advance the latest pending target exactly once. A duplicate notification must not invalidate a running preflight; a successful result should start the Claude ACP connection.

Relevant logs

Captured on 2026-09-07 at 16:13:46 UTC+8. Timestamps below are UTC. Only relevant excerpts are included; unrelated conversation/configuration contents are omitted.

wta-main_helper-19484.log, lines 222-230 and 234

2026-09-07T08:13:46.490217Z DEBUG wta::helper::runtime: wt_event_rx: received event method=retire_agent_sessions
2026-09-07T08:13:46.499710Z DEBUG alive_mirror: alive session removed by master session_id=9ca0a442-c380-49e7-a98b-83399b0800d6
2026-09-07T08:13:46.683118Z DEBUG wta::helper::runtime: wt_event_rx: received event method=rebind_agent
2026-09-07T08:13:46.696668Z  INFO helper: ending helper ACP connection for Agent rebind operation_id=56828-operation-1 generation=1 agent_id=claude
2026-09-07T08:13:46.697111Z DEBUG acp: ACP handle_io completed (over pipe) (t+129.788s)
2026-09-07T08:13:46.697175Z  WARN helper: ACP I/O loop to master ended — pipe closed (master gone)
2026-09-07T08:13:46.697783Z  INFO agent_rebind: old ACP transport retired; preflighting latest pending target operation_id=56828-operation-1 generation=1 agent_id=claude
2026-09-07T08:13:46.700751Z DEBUG agent_rebind: ignoring reconnect-ready event with no disconnect pending operation_id=56828-operation-1 generation=1
2026-09-07T08:13:46.711506Z DEBUG agent_rebind: ignoring stale target preflight result operation_id=56828-operation-1 generation=1 agent_id=claude
...
2026-09-07T08:13:53.921853Z DEBUG ui_trace: slow: state=Connecting("Starting agent...") turn=Discriminant(0) messages=0 completed_turns=0 input_chars=0 pending_chars=0 scroll=0 activity_frame=0 recommendations=0 permission=false timing_note=false scope="terminal_flush" elapsed_ms=107

The preflight result is for the same operation and generation, not an older user selection.

wta-main_master.log, lines 4451, 4456-4457

2026-09-07T08:13:46.493436Z  INFO master: physically closed replaced ACP session step="helper→agent" op="close_replaced_session" helper_id=HelperId(1) old_session_id=9ca0a442-c380-49e7-a98b-83399b0800d6 outcome="closed" elapsed_ms=2
2026-09-07T08:13:46.697518Z  INFO master: helper ownership retired; automatic recovery disabled helper_id=HelperId(1) sessions_owned=0 sessions_fallback_retired=0 intentional_close=true
2026-09-07T08:13:46.697613Z  INFO master: helper connection task exited cleanly helper_id=HelperId(1) live_helpers=0

This is intentional retirement during rebind, not evidence that the master crashed. The helper warning's parenthetical master gone is misleading in this sequence.

Root cause indicated by the code and logs

App::begin_pending_agent_reconnect_preflight starts with:

let AgentReconnectState::Disconnecting(latest) =
    std::mem::take(&mut self.agent_reconnect_state)
else {
    return None;
};

mem::take resets the state to Idle before checking its variant. Both AgentTransportRetired and AgentReconnectReady call this helper:

  1. The first notification consumes Disconnecting and starts Preflighting.
  2. The second calls the helper again, takes the Preflighting value, and returns None, silently replacing it with Idle.
  3. AgentReconnectPreflightComplete requires a matching Preflighting state, so it rejects the valid completion and never sets pending_acp_start.

Source references at the tested revision:

  • tools/wta/src/app.rs:3724-3747 - destructive state extraction.
  • tools/wta/src/app_events.rs:596-615 - reconnect-ready handler.
  • tools/wta/src/app_events.rs:1398-1413 - transport-retired handler.
  • tools/wta/src/app_events.rs:1972-2007 - preflight result matching.

The logic is provider-independent, so Claude is the observed target rather than the only potentially affected agent. The failure occurs before replacement ACP startup and is separate from hook-listener availability or its retry limit.

Proposed fix and regression coverage

  • Only consume the reconnect state when it is Disconnecting; preserve Preflighting and other states on a no-op call.
  • Cover AgentTransportRetired -> AgentReconnectReady -> PreflightComplete and the reverse notification ordering.
  • Duplicate completion notifications must not clear a pending preflight or start a second one.
  • Preserve latest-generation selection; genuinely stale results must remain ignored.
  • A valid failed preflight must still show the appropriate setup/error UI rather than leave the pane connecting.

No code or user settings were changed as part of filing this issue.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions