Tailscale SSH + tmux gets you the terminal agents. Sometimes you also
need the graphical desktop — to seed a permission grant, click a GUI
the agent can't reach, or watch a dev app live from your phone. This is
the macOS Screen Sharing path (the built-in VNC server the Screens
app and Finder's "Screen Sharing" connect to), tunnelled over Tailscale.
Two user-facing scripts live in bin/:
agent-screen— toggle Screen Sharing on/off with an auto-off lease, and forward it over the tailnet withtailscale serve.vncfix— recover the "connected, but black screen + cursor" symptom.
The installer also places a root-owned helper at
/usr/local/libexec/ss-on-demand. The sudoers rule permits only
on 1 through on 8 and off. The helper owns the absolute expiry,
the Screen Sharing daemon, and the exact Tailscale mapping. A launchd
watchdog checks the deadline every minute and at startup, so access
still closes after logout, sleep, reboot, or a failed user process.
install.sh symlinks these into /usr/local/bin alongside the agent
wrappers, so a normal install puts agent-screen and vncfix on your PATH.
To link them by hand (or
without re-running the installer):
ln -sf "$PWD/bin/agent-screen" /usr/local/bin/agent-screen
ln -sf "$PWD/bin/vncfix" /usr/local/bin/vncfixBoth resolve their own path at runtime, so either location works.
agent-screen on [HOURS] # default 1 hour; turns Screen Sharing ON
agent-screen off # turn OFF now + cancel any pending auto-off
agent-screen status # daemon, lease, mapping, and capture-grant status
agent-screen on does four things:
- Stops Remote Management (ARD) — it shares port 5900 and breaks Screens auth.
- Bootstraps + enables the
com.apple.screensharingLaunchDaemon. - Maps the configured tailnet port (default
15900) tolocalhost:5900viatailscale serve. - Stores a root-owned absolute deadline enforced by launchd.
It removes both the daemon exposure and the Tailscale mapping at the
deadline. Re-run agent-screen on to extend. The command accepts one through
eight hours and does not prompt for a password after installation.
agent-screen on prints a clickable URL using the short hostname derived from
Self.DNSName in tailscale status --json and the configured port:
screens://macbook-air:15900
The command refuses to open access if it cannot resolve the current Tailscale hostname. On a host whose inbound IPv4 is blackholed (see below), the MagicDNS hostname normally selects the working tailnet route. For manual diagnostics, the equivalent IPv6 form is:
vnc://[fd7a:115c:a1e0::xxxx:xxxx]:15900
The installer can register one shared agent-help MCP server with
Copilot CLI, Claude Code, and Codex CLI. Its only tool is
request_help.
When an agent is blocked on a login, permission, or decision:
- The server validates a fixed reason and a short label.
- It sends at most three messages per hour and deduplicates an identical request for ten minutes.
- It derives the originating agent from the NATO-alphabet tmux session name; callers cannot replace that identity with a project or phase label.
- It runs
agent-screen on SCREEN_SHARING_HOURSand adds the server-derived Screens URL to the iMessage.
The server cannot read Messages, choose a recipient, accept an
agent-provided URL, or run message content. The private recipient and
rate-limit state live under
~/.config/remote-agent-stack/agent-help/ with private permissions.
You connect, see the cursor move, but the rest is black. Two causes:
- Daemon/clamshell glitch — the screen-sharing daemon + per-user
ScreensharingAgentneed a kick.vncfixrestarts both; Screens refreshes in a couple seconds. - Missing Screen Recording grant — macOS 14+ captures the
framebuffer via ScreenCaptureKit, which needs the screen-sharing
agent to hold a ScreenCapture (Screen Recording) TCC grant. Without
it the agent logs
Cannot get shareable contentand you get cursor-over-black. This cannot be fixed from a shell (SIP blocks all writes toTCC.db, and the grant needs interactive admin auth).
vncfix does the daemon/agent restart, then scans the agent log; if it
finds the Screen-Recording denial it explains the manual fix and opens
System Settings → Sharing for you:
System Settings → General → Sharing → toggle Screen Sharing OFF, wait 2s, toggle it back ON (authenticate when asked).
On a fresh Mac this grant has never been seeded, so the first Screens connect is almost always black until you do this once. After that it persists. If you're fully remote with no way to authenticate, power on the JetKVM and do it through that.
On some Jamf/MDM-managed Macs, inbound Tailscale IPv4
(100.64.0.0/10) is silently dropped — ping and TCP both time out —
while IPv6 works fully. This persists across tailscaled restarts
and reboots; it's an enforced packet filter, not wedged daemon state.
Symptom: you can SSH/connect out fine, and other nodes are
reachable, but nothing can reach this host on its 100.x address.
Workaround: reach the host over its fd7a:… IPv6 address (Screens,
SSH, tailscale serve all work over v6). A real fix needs an IT/MDM
exception for the tailnet CIDR.
If you keep the Mac awake (e.g. Amphetamine) so agents can screenshot dev apps for you, but want the physical displays off for privacy:
- Don't let the display sleep or the screensaver fire — an MDM policy may lock immediately on display-off, and a locked screen is all the agents would capture.
- Don't lower the requirement to "all displays off" — if the framebuffer tears down (every display powered off/disconnected with no internal panel), captures go genuinely black.
The clean setup uses the JetKVM as an always-on virtual display:
- The JetKVM is an HDMI capture device with EDID, so macOS always sees it as a real attached display. Keep it plugged in.
- Leave it in Mirror mode (not Extended). Mirror means remote control through the JetKVM shows your real desktop, and when you power off the external monitors the mirror set collapses onto the JetKVM — a display is still attached, so the Mac never hits "all displays off" (no lock) and the framebuffer stays alive (screenshots keep working).
- Agent screenshots are taken per-window
(
screencapture -x -o -l<winid>), which reads the WindowServer backing store and is display-agnostic — it works regardless of which display a window is on, or whether any monitor is powered.
Net: power the external monitors off on a timer; keep the JetKVM connected in Mirror; keep Amphetamine suppressing display-sleep so the JetKVM display itself never sleeps.