feat(agent): Linux remote desktop (X11 mirror, pure-Go) + Phase 0 bugfixes#2602
Merged
Conversation
Field investigation of an Ubuntu 22.04 xrdp VM + full code map showed Linux remote desktop has never worked in released builds (CGO_ENABLED=0 strips the X11 capturer) plus six compounding defects. Spec covers Phase 0 correctness fixes, Phase 1 X11 mirror via purego runtime loading, Phase 2 Linux desktop-helper + Wayland portal/PipeWire, Phase 3 deferred session creation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-agent research + adversarial verification corrected five load-bearing assumptions in the draft: - X11 mechanism: jezek/xgb pure-Go wire protocol, NOT purego/Xlib (Xlib exit()s the whole agent when a mirrored X session dies — the xrdp case this targets) - per-capturer-instance X state (delete C-global g_ctx) — the single-session invariant holds only inside SessionManager; WS/screenshot/monitor-switch/cursor all run concurrently - desktopAccess mode must be 'user_session' not 'available' (zod mode enum has no .catch — 'available' silently drops the whole object on deployed servers) - dynamic isHeadless needs state-based stop routing + unconditional OnSessionStopped + atomic (plain-bool per-tick write races pool workers) + static-screen watchdog - Phase 0 thrash fix: snapshot wasInstalled before migrateFromLegacyName; flag the Linux legacyBinaryPath==defaultBinaryPath collision for Phase 2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TDD task breakdown (15 tasks) grounded in multi-agent research + adversarial verification of the spec. Phase 0 = 3 independent bugfix PRs (Assist thrash, darwin-spawn-on-Linux, honest error). Phase 1 = X11 mirror via jezek/xgb pure-Go wire protocol: Xauthority cookie parser, display/session resolver, per-instance SHM capture, XTEST input, XFixes cursor, RandR monitors, dynamic-headless routing with state-based stop + static-screen watchdog fix, desktopAccess reporting, API + shared reason enum, 5-locale web reasons, and rig verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… heartbeat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…interim) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
initShm() created the MIT-SHM segment with IPC_CREAT|0o777, making the captured framebuffer world-readable/writable to any local user. Create the segment as 0600 instead, and chown it to the session owner's uid (DisplayTarget.OwnerUID) so the non-root X server on xrdp/GDM can still attach while other local users cannot. Best-effort: if the chown fails or the owner uid is unknown, shm.AttachChecked fails cleanly and initShm already falls back to core-protocol capture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…[correctness] Capture() and CaptureRegion() released c.mu before calling conn.CaptureBGRX()/CaptureRegionBGRX() and copying the pixel buffer, so an in-flight capture was not mutually excluded from Close() (which unmaps the SHM segment and nils the connection under the same lock). On the WS stop path, Stop() closes the capturer without joining the ~30fps capture-loop goroutine, so this could SIGSEGV (read of unmapped SHM) or nil-pointer panic. Hold c.mu for the full body of both methods, matching the pattern already used by GetScreenBounds()/Close(). Also harden mapResolveErr's Wayland-unsupported check with errors.Is instead of == for robustness against future error wrapping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, disconnect notify, static-screen watchdog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…M cleanup Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deploying breeze with
|
| Latest commit: |
7241a40
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1bb81eb8.breeze-9te.pages.dev |
| Branch Preview URL: | https://toddhebebrand-ubuntu-breeze.breeze-9te.pages.dev |
… helper Fixes the Lint Agent (Go) govet finding on the Task 11 test helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
golangci-lint runs on Linux CI and lints the //go:build linux files that a darwin dev machine skips; add _ = to the cleanup Close()/error returns flagged by errcheck across the x11 package + monitor/capture linux files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eQL) The SHM segment owner UID traces from strconv.Atoi (loginctl parse) to a uint32 conversion; add an explicit <= math.MaxUint32 guard so an out-of-range value skips the chown (falling back to core-protocol capture) instead of truncating. Resolves the CodeQL go/incorrect-integer-conversion high alert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes Linux remote desktop actually work. It has never functioned in any released build — release agents are
CGO_ENABLED=0, but the only Linux capturer was behind//go:build linux && cgo, so every shipped agent contained theErrNotSupportedstub (confirmed vialddon a deployed v0.96.0 agent — no libX11 linkage). This branch delivers a working Linux X11 mirror plus three independent correctness bugfixes.Full design + implementation plan:
docs/superpowers/specs/2026-07-16-linux-remote-desktop-design.md,docs/superpowers/plans/2026-07-16-linux-remote-desktop-phase0-1.md.Phase 0 — correctness bugfixes (all platforms)
migrateToSessions()(recreatingsessions/,pkill-ing helpers, rewriting autostart) thenuninstallLocked()deleted it again — an endless per-tick loop fleet-wide, on every platform. Gated the migration onsettings.Enabled || wasInstalled(snapshot taken beforemigrateFromLegacyNamecan delete the binary).spawnHelperForDesktopgated on!= "windows"→ now== "darwin", with a typedErrLinuxDesktopHelperUnsupportedfast-fail).start_desktop(superseded by real capture in Phase 1).Phase 1 — Linux X11 mirror (pure-Go, no CGO)
Speaks the X11 wire protocol via
github.com/jezek/xgbv1.3.1 — no C libraries, so it ships in theCGO_ENABLED=0release build. (Chosen over purego/Xlib because Xlib callsexit(1)when the X connection dies — exactly the xrdp-logout case this targets — which is inexpressible to escape from Go; xgb turns it into a plain read error.)internal/remote/desktop/x11package: Xauthority cookie parser, display/session resolver (finds the live X display via/proc, resolves its owner + Xauthority), per-instance MIT-SHM capture, XTEST input (xdotool removed), XFixes cursor, RandR monitors. All X state is per-capturer-instance (the old C-globalg_ctxis gone), which is what makes concurrent capturers safe.0600and chowned to the X-server owner UID — the agent runs as root but Xorg runs as the session user, so a naive root-owned0600would fail to attach; this keeps the framebuffer off other local users while still letting the non-root X server attach.start_desktop/screenshot/computer-action to the direct capturer path; stop routing is state-based;OnSessionStoppedregistered on Linux; static-screen watchdog fixed so an idle desktop isn't killed.desktopAccesscapability report (real connect probe, not just resolve):no_display_session/wayland_unsupported/x11_connect_failed/x11_auth_failed, surfaced as localized "Connect Desktop" tooltips in all five locales.Verification
Built with per-task spec + quality review (each task reviewed, the capturer's close-race and the SHM world-readable finding both caught and fixed in review). Builds green on linux/darwin/windows; all unit suites pass.
Rig-verified on real hardware (Ubuntu 22.04, non-root X server + root agent, the faithful xrdp config):
ipcs): capture segment =owner=<sessionuser> perms=600 8.3MB nattch=2— zero-copy, not world-readable, chowned to the X-server owner.Not in this PR (follow-ups)
clipboard_linux.gocode exists (text/RTF/images) but needs display-context plumbing; coming as a fast follow-up (text-first, pure-Go xgb selections).legacyBinaryPath()==defaultBinaryPath()Linux guard — Phase 2 (separate plan).DeviceInfoTabdesktopAccess Section is macOS-gated (button tooltip is the live Linux surface); per-AI-call input-handler connection (finalizer-mitigated).Rollout note
Linux desktop mirrors an existing X session — it can't create one. On a headless/xrdp box a user must log in (RDP/console) first so a session exists; with no session the Connect Desktop button is greyed with a "no active graphical session" tooltip.
🤖 Generated with Claude Code