Describe the bug
A session can end a turn and enter a permanently wedged state: neither idle nor
running. It accepts no further input, the app shows it as stopped, and queuing a
message into it silently does nothing. The only recovery is to kill the process.
The process does not crash. It stays alive, Responding=True, at zero CPU, with
no ERROR or WARN line anywhere in its log, and its last session events are a
clean assistant.turn_end. It is parked, not looping and not faulted.
The signature is a single log line, emitted 91 ms after the final turn end:
2026-09-07T08:44:37.505Z assistant.turn_end (events.jsonl)
2026-09-07T08:44:37.596Z [DEBUG] [rust:copilot_runtime::session::registry]
Session idle finalization suppressed
{"queue_processing":false,"queued_lane":true,"immediate_lane":false,"host_pending_send":false}
A message arrived on the queued lane while the turn was running. At turn end
the registry correctly declined to finalize the session as idle because the lane
was non-empty, but the queue processor was never started (queue_processing: false) and nothing ever starts it. So the session cannot go idle (the queue is
non-empty) and cannot drain the queue (nothing is processing it). A lost wakeup
between turn-end idle finalization and the queued-lane drain.
The immediate lane does not rescue it. After the wedge I sent the session a
send_session_message with delivery_mode: "immediate". It was recorded as a
pending_messages_modified telemetry event and then never delivered, never
written to events.jsonl, and never processed. Once wedged, both lanes are dead.
Which sessions hit it. I grepped all 62 process logs on this machine for the
suppression line. It appears in exactly three, and all three are the same
workload shape: a long-running parent session that spawns several child sessions
with coordinate_with_creator: true and then runs multi-minute turns, so it
takes inbound cross-session messages at unpredictable moments. The examined
session made 12 create_session calls and received 11 inbound cross-session
messages before wedging.
The lane is the discriminator. In that same session, three earlier inbound
cross-session messages arrived as delivery="steering" (the immediate lane, at
08:32:00Z, 08:33:46Z and 08:34:30Z) and were all consumed normally mid-turn. The
message that wedged the session went to the queued lane. Since
send_session_message defaults to delivery_mode: "enqueue", any child session
replying to its parent with the default lands on the lane that can wedge.
That makes this reachable by ordinary multi-session orchestration, which is a
documented feature, not an exotic configuration.
Affected version
1.0.83-5 (also observed on the immediately preceding build; three occurrences
across 2026-09-06 and 2026-09-07). Running inside the GitHub Copilot desktop app,
which drives this CLI as one process per session.
Steps to reproduce the behavior
It is a race, so it reproduces probabilistically rather than deterministically.
Three occurrences in two days on a machine running this pattern continuously.
- Start a parent session and have it spawn several child sessions with
create_session, passing coordinate_with_creator: true.
- Keep the parent busy in long turns (multiple minutes each).
- Have the children report back with
send_session_message using the default
delivery_mode (enqueue), at times they choose, so at least one message
lands close to the instant a parent turn ends.
- Occasionally the parent's final turn end logs
Session idle finalization suppressed with queued_lane: true and queue_processing: false, and the
session is wedged from that moment on.
To confirm a wedge rather than a long turn: the process is alive and responding,
CPU is flat at zero, the log has no error, and no further events are appended to
events.jsonl.
Expected behavior
Suppressing idle finalization because the queued lane is non-empty should
guarantee the queue processor is subsequently started. The two decisions want to
be one atomic transition, so a session cannot end up in a state where it is
ineligible for idle and has nothing scheduled to drain the queue.
Failing that, either of these would make it self-healing rather than terminal:
- a watchdog that re-checks a session which is non-idle with
queue_processing: false and no in-flight turn, and starts the drain;
- an
immediate-lane delivery forcing a drain attempt, so a wedged session can
be recovered by messaging it instead of by killing the process.
It would also help if pending_messages_modified were not reported as success to
the sender when the target session can never consume the message. From the
caller's side the send appears to succeed.
Additional context
Recovery, for anyone else who hits this: find the pid from the
inuse.<pid>.lock file in ~/.copilot/session-state/<sessionId>/ and stop that
process. The app restarts the session, replays a "Continue from where you left
off" prompt, and resumes normally. events.jsonl history and the git worktree
are untouched; only the undelivered pending messages are lost, and they were
never going to be delivered. I confirmed this: the wedged session restarted and
took a new turn 3 minutes later.
Diagnosis is straightforward if you have the logs, since the suppression line
names the exact state:
Get-ChildItem ~/.copilot/logs/*.log |
ForEach-Object { Select-String -Path $_ -Pattern 'Session idle finalization suppressed' }
Environment
- Operating system: Windows 11 Enterprise
- CPU architecture: AMD64 (x86_64)
- Host: GitHub Copilot desktop app (one CLI process per session), sessions are
git worktree-backed
- Shell: PowerShell
Observed impact. The wedged session was coordinating a multi-session
workstream, so the failure is not merely a lost message: the parent stops
supervising children that are still running, and there is no signal to the user
beyond the session appearing stopped. I have had to work around it by changing
our agent definitions to send all child-to-parent reports with delivery_mode: "immediate" and to stop setting notify_on_idle, purely to keep traffic off the
queued lane. That reduces the exposure but obviously does not close the race.
Describe the bug
A session can end a turn and enter a permanently wedged state: neither idle nor
running. It accepts no further input, the app shows it as stopped, and queuing a
message into it silently does nothing. The only recovery is to kill the process.
The process does not crash. It stays alive,
Responding=True, at zero CPU, withno
ERRORorWARNline anywhere in its log, and its last session events are aclean
assistant.turn_end. It is parked, not looping and not faulted.The signature is a single log line, emitted 91 ms after the final turn end:
A message arrived on the queued lane while the turn was running. At turn end
the registry correctly declined to finalize the session as idle because the lane
was non-empty, but the queue processor was never started (
queue_processing: false) and nothing ever starts it. So the session cannot go idle (the queue isnon-empty) and cannot drain the queue (nothing is processing it). A lost wakeup
between turn-end idle finalization and the queued-lane drain.
The immediate lane does not rescue it. After the wedge I sent the session a
send_session_messagewithdelivery_mode: "immediate". It was recorded as apending_messages_modifiedtelemetry event and then never delivered, neverwritten to
events.jsonl, and never processed. Once wedged, both lanes are dead.Which sessions hit it. I grepped all 62 process logs on this machine for the
suppression line. It appears in exactly three, and all three are the same
workload shape: a long-running parent session that spawns several child sessions
with
coordinate_with_creator: trueand then runs multi-minute turns, so ittakes inbound cross-session messages at unpredictable moments. The examined
session made 12
create_sessioncalls and received 11 inbound cross-sessionmessages before wedging.
The lane is the discriminator. In that same session, three earlier inbound
cross-session messages arrived as
delivery="steering"(the immediate lane, at08:32:00Z, 08:33:46Z and 08:34:30Z) and were all consumed normally mid-turn. The
message that wedged the session went to the queued lane. Since
send_session_messagedefaults todelivery_mode: "enqueue", any child sessionreplying to its parent with the default lands on the lane that can wedge.
That makes this reachable by ordinary multi-session orchestration, which is a
documented feature, not an exotic configuration.
Affected version
1.0.83-5(also observed on the immediately preceding build; three occurrencesacross 2026-09-06 and 2026-09-07). Running inside the GitHub Copilot desktop app,
which drives this CLI as one process per session.
Steps to reproduce the behavior
It is a race, so it reproduces probabilistically rather than deterministically.
Three occurrences in two days on a machine running this pattern continuously.
create_session, passingcoordinate_with_creator: true.send_session_messageusing the defaultdelivery_mode(enqueue), at times they choose, so at least one messagelands close to the instant a parent turn ends.
Session idle finalization suppressedwithqueued_lane: trueandqueue_processing: false, and thesession is wedged from that moment on.
To confirm a wedge rather than a long turn: the process is alive and responding,
CPU is flat at zero, the log has no error, and no further events are appended to
events.jsonl.Expected behavior
Suppressing idle finalization because the queued lane is non-empty should
guarantee the queue processor is subsequently started. The two decisions want to
be one atomic transition, so a session cannot end up in a state where it is
ineligible for idle and has nothing scheduled to drain the queue.
Failing that, either of these would make it self-healing rather than terminal:
queue_processing: falseand no in-flight turn, and starts the drain;immediate-lane delivery forcing a drain attempt, so a wedged session canbe recovered by messaging it instead of by killing the process.
It would also help if
pending_messages_modifiedwere not reported as success tothe sender when the target session can never consume the message. From the
caller's side the send appears to succeed.
Additional context
Recovery, for anyone else who hits this: find the pid from the
inuse.<pid>.lockfile in~/.copilot/session-state/<sessionId>/and stop thatprocess. The app restarts the session, replays a "Continue from where you left
off" prompt, and resumes normally.
events.jsonlhistory and the git worktreeare untouched; only the undelivered pending messages are lost, and they were
never going to be delivered. I confirmed this: the wedged session restarted and
took a new turn 3 minutes later.
Diagnosis is straightforward if you have the logs, since the suppression line
names the exact state:
Environment
git worktree-backed
Observed impact. The wedged session was coordinating a multi-session
workstream, so the failure is not merely a lost message: the parent stops
supervising children that are still running, and there is no signal to the user
beyond the session appearing stopped. I have had to work around it by changing
our agent definitions to send all child-to-parent reports with
delivery_mode: "immediate"and to stop settingnotify_on_idle, purely to keep traffic off thequeued lane. That reduces the exposure but obviously does not close the race.