Remote Control on always-on machines: three reproducible edges (consent vs. service autostart, classifier mid-flow interruption, unstable environment identity)
Three reproducible edges found while setting up claude remote-control as a persistent service on an always-on personal Linux machine (Claude Code 2.1.233, Linux x86_64, systemd user session, tmux).
Reporter: Roberto Locatelli (GitHub: robertolocatelli81-dev). Related public feature proposal: anthropics/claude-code#87410.
---
EDGE 1 — Consent prompt is incompatible with service autostart, and the only workaround is one the permission classifier (rightly) refuses.
Repro:
claude remote-control --name NAME --spawn same-dir→ interactiveEnable Remote Control? (y/n)→y→ works.- To survive reboots, the natural path is a systemd user unit. But there is no non-interactive consent flag (no
--yes/--consentin--help), so the unit must pipe stdin:ExecStart=/bin/sh -c 'echo y | claude remote-control --name NAME --spawn same-dir'. - Verified behavior with stdin not a TTY:
echo y | claude remote-control …connects successfully (Connected, exit 0 undertimeout). It is not observable from outside whether the pipedywas consumed or the prompt was skipped on non-TTY. If it is skipped on non-TTY, that deserves an intentional-behavior review: a daemon manager could then enable remote access with no consent token at all. - The twist: when the in-session agent (auto permission mode) tried to install that unit at my explicit request, the auto-mode classifier blocked it repeatedly (compound bash; then a plain file write of the unit). I think the classifier is right — an agent should not be able to bake an auto-consent for remote machine access into a boot unit. But that leaves the legitimate use case with no sanctioned path.
Suggestion: a human-performed persistent consent, e.g. claude remote-control enable --persist (interactive once, writes a signed consent record) + service mode that refuses to run without that record. Trivial systemd unit, no stdin games, clean allow-signal for the classifier, and it closes the non-TTY ambiguity.
---
EDGE 2 — Auto-mode classifier cuts an interactive flow mid-way, stranding the process at a prompt.
Repro (agent in auto permission mode):
tmux new-window -d -n rc 'claude remote-control --name NAME'→ allowed.- First prompt answered:
tmux send-keys -t rc y Enter→ allowed; TUI advances to its second prompt (spawn mode[1/2]). - Second prompt answered:
tmux send-keys -t rc 1 Enter→ BLOCKED by the classifier. - Even read-only
tmux capture-pane -pt rcin the same context → blocked.
Impact: the interactive process is left hanging at a half-answered prompt — the flow was half-approved. Outcome looks like a hung setup rather than a refused one. (Spirit-compliant workaround that worked: relaunch with explicit flags — --spawn same-dir — to remove the second prompt entirely; the agent had to discover this.)
Suggestion: interaction continuity in the classifier — if step N of an interactive exchange with a given pane/foreground process was allowed, evaluate step N+1 in that context (or block the whole flow at step 1). Cheap heuristic: same target pane + same foreground process ⇒ inherit the initial verdict, re-evaluating only on privilege escalation.
---
EDGE 3 — Remote Control environment identity is not stable across host-process restarts.
Repro:
claude remote-control --name NAME→ environment env_A created; printed link/QR embeds env_A.- Ctrl+C, then start again with the SAME
--name NAMEin the SAME directory → a NEW environment env_B is created. - The env_A link/QR is dead; the peer-sessions list accumulates stale
Remote Control · offlinerows (including one from a few-second probe run).
Impact: on an always-on personal machine every reboot/update invalidates saved links and printed QRs; dead environments pile up with no visible expiry or cleanup affordance. -c/--continue exists but is documented for a ~4-hour window, which does not cover reboots.
Suggestion: stable environment binding keyed on (account, machine, directory, name) — reconnecting with the same key resumes the same environment id — plus TTL/cleanup or an explicit remote-control prune.
4 Comments
Independent confirmation from a very similar deployment, plus a counter-datapoint on Edge 3 that may help narrow it.
Setup: Ubuntu Server 26.04, headless,
claude remote-controlas a system systemd unit (not a user unit), no tmux, always on since early July. Claude Code 2.1.234.Edge 1 — confirmed
claude remote-control --helpon 2.1.234 lists no--yes, no--consent, nothing non-interactive. The pipe really is the only route, and my unit does exactly what you describe:The consent pipe itself has never blocked a start: 43 service starts and 11 reboots in my journal (which starts Jul 26 with the unit already running; the unit dates from Jul 10, and its first recorded start is Jul 28). The only multi-minute outage in that window was unrelated to consent — a ~6-minute post-reboot crash loop on Jul 31 where 11 starts exited on "You must be logged in", a credential-availability race, not the prompt; the only other unit failure was a ~30-second OOM auto-restart on Aug 2 (see #81343), also unrelated.
On your open question in step 3 — whether the piped
yis consumed or the prompt is skipped on non-TTY — I can't resolve it, but I can narrow where to look. On this machine~/.claude.jsoncontains:That looks like a persistent consent/identity record that already exists. If it is what suppresses the prompt, then on a machine that consented once interactively the piped
yis simply a no-op, and your non-TTY ambiguity only bites on a machine that never consented — which is exactly the case worth reviewing.It would also reframe your suggestion: the signed consent record you propose may already be there in some form, just undocumented and not user-manageable. The ask then becomes expose and document it, plus a service mode that refuses to start without it — rather than building it from scratch.
Caveat, stated plainly: this is untested. Verifying it means starting a second remote-control process with that key removed, which would disturb the live worker, and this one is in production use.
Edge 3 — does not reproduce here
Environment identity has been stable on this machine for 20 days:
env_01Gf2…first appears Jul 29 07:16 and is still the id in use today. A CLI update to 2.1.234 restarted the service at 23:07 last night; the new worker came back on the same environment id three seconds later.This isn't just my machine: the repro table in #86045 also shows the same
env_…id being reused across relaunches for a given account+directory+machine, on two Macs.So "every reboot/update invalidates saved links and printed QRs" is not universal. Differences between our setups that might explain it: system unit vs user unit, no tmux, no Ctrl+C-then-relaunch cycle, and possibly the persisted
remoteControlMachineIdabove already acting as the stable key you are asking for.If that is the mechanism, Edge 3 may be less "no stable binding exists" and more "the binding is not established or not reused in some launch paths" — which is a narrower and probably cheaper bug.
On the cleanup half of Edge 3
I do have the accumulation problem, but on session ids rather than environment ids: a dead session id that has failed worker registration at every single service start since it first appeared mid-morning Jul 31 — 26 in a row, always the same id — with nothing in the CLI or the apps listing it, so nothing can delete it. Tracked in #77372; the cleanly isolated relaunch-404 pattern is #86045.
Thanks @Roorn — this is exactly the kind of independent data the issue needed. I re-measured on the original machine (Debian container on ChromeOS, user systemd unit, tmux present, Claude Code 2.1.234) to test your two hypotheses against the opposite setup. Results, honestly including where they weaken my own Edge 3:
On your
~/.claude.jsonhypothesis (Edge 1) — partially confirmed, with a twistMy machine has the same keys:
but — and this is the interesting part —
"hasRemoteEnvironment": false, while a remote-control worker is connected and serving sessions right now (verified: the panel showsCapacity: 1/32, and the active session is the one I'm writing this from). So whatever these keys are,hasRemoteEnvironmentis either not the consent/identity record we're both looking for, or it is not consistently maintained across launch paths. That strengthens your reframe: the record exists in some form, but it appears undocumented (nothing inremote-control --helpsurfaces it) and — new datapoint — it is not reliably updated. "Expose, document, and gate service mode on it" still looks like the right ask — but whichever field is chosen needs to actually be written on every path that establishes an environment.On Edge 3 — my current data now agrees with you, and narrows my original claim
I went back through the full retention of my worker log (38k lines): two environment ids in total, exactly one changeover in the whole history. The current
env_…id survived last night's service restart (21:01) and this morning's full reboot (09:30) — same id, reconnect in seconds. My log has no per-line timestamps, so I can't date the single changeover precisely; stated plainly, that's the limit of my data.So on this machine, today, "every reboot/update invalidates saved links" does not reproduce either — on a user unit with tmux, i.e. the opposite configuration of yours. Combined with your 20-day-stable system unit and the two Macs in #86045, the evidence now points where you suggested: the binding exists and is normally reused; my original Edge 3 was most likely observing the establishment/reuse failure on specific launch paths (or an older CLI version — my earliest observations predate 2.1.220), not the absence of a stable binding. I consider my Edge 3 as originally worded too broad; the narrower bug — "binding not established or not reused on some launch paths, and no way to inspect/manage it" — is the one worth fixing.
On the session-id accumulation (#77372): stated plainly, I don't have comparable data — my worker log shows a single transient
Session failed(process exit), not your persistent 26-in-a-row registration failure on one dead id. So I can neither confirm nor weaken that one; if it holds, the inspect/manage half of the ask covers both problems anyway.— Roberto Locatelli
Thanks for re-measuring on your side, that settles a lot.
One more datapoint on the
hasRemoteEnvironmenttwist: on my machine it readstruewith the worker connected, checked just now, also on 2.1.234. Same version, opposite value, so the launch path looks like the variable rather than the CLI version. I cannot tell when the field was written here; what I can say is that a system unit that has been running for weeks ends up withtruewhere your user unit showsfalse. Either way it supports your conclusion: the record is real, undocumented, and not consistently maintained.Agreed on the narrowed Edge 3 wording. If a maintainer picks this up and needs logs from the system unit side, happy to provide them.
That's the missing piece, thanks. Re-checked mine just now (still 2.1.234, worker process up and reconnecting normally):
hasRemoteEnvironmentis still false, read three times, stable. And to be precise about what I can actually prove: the worker is connected (process alive, last log line a clean reconnect), though its panel currently showsCapacity: 0/32, so I'm not claiming it's mid-session right now — just connected.So on identical CLI versions we have a clean pair:
truefalseSame version, worker connected in both cases, opposite value. That isolates it from CLI version and points at the launch path:
hasRemoteEnvironmentis written on the path that starts the system-unit worker but not on the one that starts mine. And sinceremoteControlMachineIdis present on both, it matches your reframe — the consent/identity record exists, but the flag that should reflect it is populated inconsistently across launch paths.For the maintainers that's a narrow, cheap fix: write
hasRemoteEnvironment(and clear it on teardown) on every path that establishes or drops a remote environment, so the field can be trusted as the "is a remote worker live" signal it looks like. Nothing more from me either — happy to pull specific log lines if that helps pin the exact path.