Skip to content

[compass] Fix: a detached seat can't be reconnected by the button its message names, and agents aren't told capture is down - #42

Merged
skulitom merged 5 commits into
mainfrom
compass/anode-fix-2026-10-13-detached-seat
Oct 13, 2026
Merged

skulitom merged 5 commits into
mainfrom
compass/anode-fix-2026-10-13-detached-seat

Conversation

@skulitom

@skulitom skulitom commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Report

B-038 in C:\DEV\Compass\ANODE-BUGS.md (filed 2026-10-13 by compass-play-upload): on 0.11.2, seat_status said detached, seat_screenshot and seat_capabilities reported "The seat's display is unavailable for capture. RDP may have suspended rendering while its viewer was hidden or minimized...", Chrome's page had no controls in seat_observe, and seat_display refused. The Play Console upload stopped there.

What happened (from the daemon's own log, %LOCALAPPDATA%\Packages\OpenAI.Codex_…\LocalCache\Local\Anode\anode.log)

  1. 10-10 21:47: the daemon started without --sign-in; automatic sign-in failed (2055, a PIN account); the user pressed Sign in…, entered the password, and the seat came up in session 2.
  2. 10-13 05:16: the viewer disconnected, reason 3, extended reason 17 = MS-RDPBCGR ERRINFO_CLOSE_STACK_ON_DRIVER_FAILURE, "The display driver in the remote session was unable to complete all the tasks required for startup". Anode logged the control's generic text instead ("The administrator has ended the session... A network problem occurred") and "Press Reconnect".
  3. 13:14: the agent saw only state: detached (the message reaches the viewer and log, not lastError) and a capture error pointing at a hidden viewer.
  4. 15:30: Reconnect pressed four times; each tried automatic sign-in and failed at once with 2055 ("The seat could not start"), though the seat was running. Anode was then quit, which signed the seat out and closed everything in it.

Root cause

ReconnectAsync set _promptForCredentials = promptForCredentials || _options.PromptForCredentials, so the Reconnect button forgot that the running seat could only sign in through the credential dialog. DisplayAsync already assumes such a seat needs the password for every new connection, but read the same per-attempt flag, which StartSeatAsync also resets. The detached message was the one place that said what to do, and agents never saw it.

Fix

  • Remember how the running seat signed in (_seatSignedInWithPassword, set at login, cleared when the seat stops or disappears). Reconnect asks for the password again for such a seat; DisplayAsync keeps refusing a viewer reconnect for it even after a host recovery reset the prompt. Only the viewer's buttons call ReconnectAsync; StartSeatAsync and every agent-reachable path are unchanged and never raise the dialog.
  • detached says what is down and the next step, in lastError too (so seat_status, anode status and anode start show it): screenshots fail, browser pages show no controls, the display can't change, programs and jobs keep running; for a password seat "the user opens the viewer, presses Reconnect and enters their Windows password; an agent can't do this step".
  • seat_display while the viewer is disconnected: a live change is refused with that message, and so is any change on a password seat (before, it advised anode quit, which closes the seat's programs). A reconnect on an automatically signed-in seat still goes through, since that is how it comes back, including the seat host's return to the old display after a failed one.
  • The capture error says when the seat's session is disconnected (WTSQuerySessionInformation / WTSConnectState in the seat host), pointing at seat_status; otherwise today's text is unchanged.
  • Extended disconnect reasons 15–18, 23 and 24 are named (display driver, DWM, Winlogon, CSRSS failures) instead of the control's generic description.
  • Already fixed on main, not in this PR: in 0.11.2 a start could slip in after quit (15:30:34 "Creating the seat..." then E_FAIL); [compass] Fix: anode quit leaves Anode running while the sign-in dialog is open #34's _quitting guard covers that.

Docs: TROUBLESHOOTING gains "The seat is detached and screenshots fail" (and a pointer from "Hidden viewer capture fails", and a line in "It asks for a password every time"); PROTOCOL notes detached fills lastError; CHANGELOG entry.

How it was tested

  • Implemented by Codex (gpt-6-astra, workspace-write, ~8 min) from the fixer's brief (logs/codex/briefs/2026-10-13-anode-fixer-B-038.md); I read the whole diff and rewrote the CHANGELOG entry.
  • Debug build, 0 warnings; selftest --quick 107/107 (104 + daemon detached viewer, rdp extended reasons, capture unavailable message; viewer sign-in updated: its old assertion that a plain Reconnect after Sign in drops the prompt encoded this bug).
  • The new daemon check drives an injected daemon (no window, no host pipe) through Sign in → login → ready → viewer disconnect, for a password seat and an automatic one, then Reconnect, display changes, an agent start, Stop and a vanished seat. It sets the host-ready state by reflection, since only the real host bring-up sets it.
  • Mutations (scratch/compass-anode-fixer/evidence/2026-10-13-b038-mutations*.txt): 12 of 12 caught: Reconnect forgetting the sign-in, login not recording it, detached without lastError, Stop or a vanished seat keeping it, the display check reading only the attempt's prompt, the detached guard removed, blocking an automatic seat's reconnect, or letting a live change through, the message always saying "press Reconnect", extended reason 17 unexplained, and a stop keeping the detached advice.
  • Review round 1 (Codex CLI, read-only): one P2: the detached guard also blocked the seat host's rollback reconnect after a failed display reconnect. Fixed in f636b28; the check covers it.
  • Round 2: the CLI review of 8a7f90b found nothing; the GitHub Codex connector found one P2: a detached seat stopped through Stop kept the "still running, press Reconnect" advice in lastError. Fixed in 59326e0; the check covers it.
  • Round 3: the CLI review of 59326e0 found nothing; a fresh Claude subagent found no P1/P2 and one P3 race (a failed display reconnect or lost host connection setting detached while a stop runs would leave the advice in stopped). Fixed in 751152a, with an extra assertion in the check; the subagent confirmed the delta (merge log).
  • With main ([compass] Fix: log timestamps follow the Windows culture (Thai year 2569, Finnish 23.34.16) #41) merged in (8a7f90b, final head 751152a): FOCUS test command (scripts/build.ps1 -QuickTest -OutputDirectory artifacts\pkg-build) passes, Release selftest --quick 108/108 (Debug and Release), check-docs.ps1 consistent.
  • Only quick checks ran here: no seat, start, quit, lease or anode-control call.

Needs a manual check in a real seat

  • With a seat that signed in through Sign in…: disconnect the viewer (or wait for a detach), then anode status shows the new lastError; seat_screenshot names the disconnected viewer; Reconnect opens the credential dialog, and after the password the seat is ready with its programs still open.
  • The capture message relies on the seat's session reporting WTSDisconnected while its viewer is gone; if Windows reports otherwise, the old message appears (the seat_status part still holds).

🤖 Generated with Claude Code

…ames, and agents aren't told capture is down

A seat that signed in through Sign in (Windows' credential dialog) lost its viewer (on 10-13,
extended reason 17: the seat's display driver failed). Its message said to press Reconnect, but
ReconnectAsync reset the prompt to the start option, so every press tried automatic sign-in and
failed with 2055, reporting "The seat could not start" while the seat still ran. Agents saw only
state detached, and a capture error blaming a hidden or minimized viewer.

- The daemon remembers that the running seat signed in with the password; Reconnect then asks for
  it again, and display reconnects stay refused even after a host recovery reset the prompt.
  Cleared when the seat stops or disappears. Agent-reachable starts never raise the dialog.
- detached carries its message in lastError (seat_status, anode status, anode start), saying what
  is down and the user's reconnect step; seat_display refuses while the viewer is disconnected.
- The seat host's capture error says when its session is disconnected (WTSConnectState).
- Extended disconnect reasons 15-18, 23 and 24 are named instead of the control's generic text.
- Quick checks: viewer sign-in updated; daemon detached viewer, rdp extended reasons and capture
  unavailable message added. TROUBLESHOOTING, PROTOCOL and CHANGELOG updated.

Implemented by Codex (gpt-6-astra) from the fixer's brief; reviewed and tested by the fixer.
Fixes B-038 (C:\DEV\Compass\ANODE-BUGS.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T22:32:34.149532Z 6a39612 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a39612181

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Anode/Daemon/AnodeDaemon.cs Outdated
Comment on lines 1128 to 1129
if (state is "error" or "logon-error" or "detached") _lastError = message;
else if (state == "ready") _lastError = string.Empty;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the detached error after a successful stop

When a detached seat is stopped successfully, StopSeatAsync transitions through stopping to stopped, but neither state clears _lastError. Because this change now stores every detached message there, subsequent status responses remain state: "stopped" while lastError still claims that the seat and its programs are running and instructs the user to reconnect. Clear the error on a successful stopped transition (or replace it with an appropriate stopped-state value).

Useful? React with 👍 / 👎.

skulitom and others added 4 commits October 13, 2026 23:36
Codex review (P2): the new detached guard in DisplayAsync also refused method reconnect, which
is how the seat host returns the viewer to the old display after a failed display reconnect,
and how an automatically signed-in seat comes back. While the viewer is disconnected, refuse a
live change (it can't work) and any change on a seat that signed in with the password (the user
must reconnect it), but let an automatic seat's reconnect through, as before this PR. The
detached check now covers both, and two more mutations are caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6-10-13-detached-seat

# Conflicts:
#	CHANGELOG.md
Codex connector review (P2): a detached seat stopped through Stop kept its detached message in
lastError, so status said "stopped" while lastError still said the seat was running and how to
reconnect it. Moving from detached to stopping now clears it; a failed stop still records its
own error. The detached check stops a detached seat and requires an empty lastError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (fresh Claude subagent, P3): a failed display reconnect, or a lost host connection, can
set detached after Stop has moved to stopping, so the seat would end stopped with the reconnect
advice still in lastError. Stop's success path now clears it when the state it ends is detached.
The check also asserts the seat is detached before it stops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant