| name | rc-new |
| description | Spawn a new Claude Code session with remote control enabled (default: a detached background session — via screen on macOS/Linux, via a minimized PowerShell Start-Process on Windows — fully remote-safe, no approvals/windows), so it appears in the mobile Claude Code session picker. Takes an optional directory path; defaults to the current working directory. Use when the user runs /rc-new. |
Remote Control — New Session
Spawn a new, independent Claude Code session with remote control enabled, so it appears in the mobile Claude Code session picker and the user can drive it from their phone. The new session is independent of the current one and runs in any directory the user picks.
This skill is cross-platform. It branches on the host OS:
- macOS / Linux → detached
screen session (no visible window).
- Windows → backgrounded minimized console via PowerShell
Start-Process -WindowStyle Minimized (a fully hidden window is an opt-in fallback — see § 4 Win).
Both launch the new session as a child of the current session, so it inherits the current session's environment/permissions and gets a real console (so claude's interactive UI boots). Neither pops a window the user must interact with (on Windows it sits minimized on the taskbar). Use the visible-window launchers (§ Alternative) only when the user explicitly wants a desktop window.
Usage
/rc-new — start in the current working directory
/rc-new <dir> — start in the specified directory (absolute path, or one resolvable from cwd)
Step 0 — detect the host OS first
Everything below branches on OS. Detect it once at the start and pick the matching path. The reliable cross-shell signal:
- If
uname -s runs and returns Darwin/Linux → Unix path (macOS/Linux, § 3.1–6 Unix).
- If
uname -s returns MINGW*/MSYS*/CYGWIN*, OR uname is unavailable and $OS is Windows_NT (cmd/PowerShell), OR you are otherwise on Windows → Windows path (§ 3.1–6 Win).
On Windows the agent is almost always driving a PowerShell or cmd shell, not bash, so do NOT run the bash/screen blocks there — run the PowerShell blocks. On macOS/Linux do the reverse.
| OS | Platform string PLAT | Picker tag TAG |
|---|
macOS (Darwin) | macOS | [macOS] |
| Linux | Linux | [Linux] |
| Windows | Win | [Win] |
What to do when invoked (both platforms)
- Read
ARGUMENTS. If non-empty, treat as the target directory. If empty, use the current working directory.
- Resolve the directory to an absolute path. If it does not exist, tell the user and stop. Do not silently fall back to cwd.
- Unix check:
[ -d "<dir>" ]
- Windows check (PowerShell):
Test-Path -PathType Container '<dir>'
- Derive a session name from the directory's basename (e.g.
pm-brain for ~/code/pm-brain, or Editor for C:\Users\me\Editor). The basename becomes the core of the picker name and the launcher handle.
3.1 — Build the names (platform tag + handle)
Two strings, both carrying the OS so the same project open on two machines disambiguates in the picker (Editor [macOS] vs Editor [Win]). PLAT/TAG come from the table above.
DISPLAY_NAME = "<basename> <TAG>" (e.g. Editor [macOS], Editor [Win]) — passed to --remote-control; this is what shows in the mobile picker. Brackets/space are fine inside the quoted arg on both platforms.
HANDLE = rc-<basename>-<PLAT> (e.g. rc-Editor-macOS, rc-Editor-Win) — slug-safe (no spaces/brackets). On Unix this is the screen -S name (used for re-attach/kill). On Windows it is just a label for the user to identify the spawn (Windows has no screen handle); use it in your report.
Unix — compute both:
case "$(uname -s)" in
Darwin) PLAT="macOS" ;;
Linux) PLAT="Linux" ;;
MINGW*|MSYS*|CYGWIN*) PLAT="Win" ;;
*) PLAT="$(uname -s)" ;;
esac
TAG="[$PLAT]"
BASENAME="$(basename "<ABSOLUTE_DIR>")"
DISPLAY_NAME="$BASENAME $TAG"
HANDLE="rc-$BASENAME-$PLAT"
Windows (PowerShell) — compute both:
$Plat = 'Win'
$Tag = "[$Plat]"
$Basename = Split-Path -Leaf '<ABSOLUTE_DIR>'
$DisplayName = "$Basename $Tag" # e.g. "Editor [Win]"
$Handle = "rc-$Basename-$Plat" # e.g. "rc-Editor-Win"
3.5 — Pre-seed workspace trust for the target directory
So the spawned session never hangs on the interactive "Do you trust this folder?" prompt (which a remote/headless spawn can't answer — see Known limitation 2). Claude Code stores trust per-directory in ~/.claude.json (%USERPROFILE%\.claude.json on Windows) under projects[<abs-dir>].hasTrustDialogAccepted. The pre-seed is the same Node one-liner on both platforms — os.homedir() resolves correctly everywhere; only the shell quoting of the surrounding command differs. Idempotent.
Unix:
node -e '
const fs=require("fs"),os=require("os"),path=os.homedir()+"/.claude.json";
const dir=process.argv[1];
let j={}; try{j=JSON.parse(fs.readFileSync(path,"utf8"))}catch(e){}
j.projects=j.projects||{};
const p=j.projects[dir]||(j.projects[dir]={});
p.hasTrustDialogAccepted=true;
p.hasCompletedProjectOnboarding=true;
fs.writeFileSync(path,JSON.stringify(j,null,2));
console.log("trust pre-seeded for",dir);
' "<ABSOLUTE_DIR>"
Windows (PowerShell) — same script, passed via node -e with the JS in single quotes and the dir as an argument. Use the absolute path with its real separators; pass it as a script arg (do NOT interpolate it into the JS string, to avoid backslash-escaping issues):
$js = @'
const fs=require("fs"),os=require("os"),path=os.homedir()+"/.claude.json";
const dir=process.argv[1];
let j={}; try{j=JSON.parse(fs.readFileSync(path,"utf8"))}catch(e){}
j.projects=j.projects||{};
const p=j.projects[dir]||(j.projects[dir]={});
p.hasTrustDialogAccepted=true;
p.hasCompletedProjectOnboarding=true;
fs.writeFileSync(path,JSON.stringify(j,null,2));
console.log("trust pre-seeded for",dir);
'@
node -e $js '<ABSOLUTE_DIR>'
This removes one of the two remote blockers. The other — macOS Full Disk Access for a separate Terminal — only affects the Unix § Alternative launcher; it does not exist on Windows or on the default launchers.
4 — Spawn the detached session
4 (Unix) — detached screen
Run it as a child of the current session so it inherits Full Disk Access and gets a real PTY — no Terminal.app, no window, no approvals:
screen -dmS "<HANDLE>" bash -c 'export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"; cd "<ABSOLUTE_DIR>" && exec claude --remote-control "<DISPLAY_NAME>"'
<DISPLAY_NAME> (e.g. Editor [macOS]) is the picker label; <HANDLE> (e.g. rc-Editor-macOS) is the screen handle — keep the two distinct per 3.1.
-dm starts it detached (background, no window); -S "<HANDLE>" names the screen session so it can be re-attached or killed.
- The inner
export PATH matters: screen's shell is non-login and won't source ~/.zshrc, so ~/.local/bin (the usual claude install dir) must be added or the session dies with claude: command not found. If claude lives elsewhere, resolve it with command -v claude and prepend that dir.
screen is built into macOS (/usr/bin/screen) and standard on most Linux. If missing, install it or fall back to the § Alternative launcher.
Why this needs no FDA grant or trust click: the new claude is a child of the current Claude Code process, so it inherits that process's Full Disk Access — there is no separate Terminal.app bundle to lack the grant. Trust is handled by 3.5. Net result: fully remote, zero approvals.
4 (Win) — spawn a parent-surviving claude session (default)
Windows has no screen/tmux in the base install, so the native way to spawn an independent, parent-surviving claude session with a real, working console (a TUI needs a PTY) is to launch it via Start-Process. Always resolve the claude launcher explicitly first — on this machine it's claude.exe, on others it may be claude.cmd; (Get-Command claude).Source resolves whichever it is and avoids the spawned process failing on PATH.
The default path prefers Windows Terminal (wt.exe) when present (it gives a clean OS-tagged tab and a guaranteed PTY) and falls back to a minimized PowerShell console (Start-Process powershell → conhost) when wt is absent. Both branches launch the same -EncodedCommand payload — the dir and display name travel as Base64 and never appear on a raw command line (see the postmortem bullet below for why this is mandatory). Valid PowerShell 5.1 — no &&, no PS7-only operators. Run from the agent's PowerShell shell:
# <ABSOLUTE_DIR> e.g. C:\Users\me\My Projects\Editor (spaces are fine — dir and name never touch a raw command line)
# <DISPLAY_NAME> e.g. Editor [Win] (carries the OS tag, from 3.1)
$Dir = '<ABSOLUTE_DIR>'
$DisplayName = '<DISPLAY_NAME>'
# Resolve the claude launcher (.exe or .cmd) so the spawned process never fails on PATH.
$claude = (Get-Command claude -ErrorAction SilentlyContinue).Source
if (-not $claude) { throw 'claude launcher not found on PATH — install Claude Code or add it to PATH.' }
# Build the inner command ONCE, ship it as -EncodedCommand (Base64 of UTF-16LE).
# Single-quote each value for PowerShell, doubling any embedded single quotes.
$dirQ = "'" + ($Dir -replace "'","''") + "'"
$claudeQ = "'" + ($claude -replace "'","''") + "'"
$nameQ = "'" + ($DisplayName -replace "'","''") + "'"
$inner = "Set-Location -LiteralPath $dirQ; & $claudeQ --remote-control $nameQ"
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
$wt = (Get-Command wt.exe -ErrorAction SilentlyContinue).Source
if ($wt) {
# Windows Terminal present: new tab titled with the [Win] tag, running the encoded launcher.
# ArgumentList is ONE pre-quoted string — never an array (see postmortem bullet).
Start-Process -FilePath $wt -ArgumentList "-w 0 nt --title `"$DisplayName`" -d `"$Dir`" powershell.exe -NoExit -NoProfile -EncodedCommand $encoded"
} else {
# No Windows Terminal: minimized PowerShell console (conhost), parent-surviving, real PTY.
Start-Process -FilePath 'powershell.exe' -WindowStyle Minimized -ArgumentList "-NoExit -NoProfile -EncodedCommand $encoded"
}
claude resolution is mandatory, not optional. (Get-Command claude).Source returns the real launcher path (...\claude.exe or ...\claude.cmd); passing that absolute path as the command/-FilePath means the spawn never dies with "cannot find the file." The throw aborts cleanly with a clear message instead of spawning a doomed window.
- Postmortem (2026-07-03, verified via WMI) — why
-EncodedCommand is mandatory, not style. An earlier version of this section passed the display name through argument arrays and shipped broken sessions. THREE separate layers each strip quotes when re-joining arguments with spaces: (1) PS 5.1 Start-Process -ArgumentList joins array elements with plain spaces, no quoting; (2) powershell.exe -Command re-joins its remaining argv tokens the same way; (3) wt re-joins its command tail the same way. Net effect observed: claude.exe received --remote-control Editor [Win] (unquoted) → the picker name became Editor and [Win] was consumed as a positional arg = the session's first prompt. The only channel that survives all three layers is -EncodedCommand — the command travels as Base64 with no spaces at all.
wt branch: -w 0 nt opens a new tab in the existing window (or a new window if none); --title "<DISPLAY_NAME>" puts the OS-tagged name on the tab; -d "<Dir>" sets the working directory. Those two are the only space-containing tokens left, and they're safe: ArgumentList is a single pre-quoted string passed verbatim, and wt consumes --title/-d itself rather than forwarding them to the child. The command tail (powershell.exe -NoExit -NoProfile -EncodedCommand <base64>) has no spaces inside any token, so wt's quote-stripping tail reconstruction can't damage it. -NoExit keeps the tab open if claude dies on boot, so errors stay visible.
- Fallback branch: same encoded launcher in a minimized conhost console — parent-surviving, genuine PTY. Minimized (not hidden) is deliberate — a fully hidden process can silently fail to get the PTY
claude needs (see Optional below).
- OS tag both places: the
wt branch puts <DISPLAY_NAME> (Editor [Win]) on the tab title and inside the encoded --remote-control arg; the fallback carries it in the encoded arg. Either way the mobile picker shows the OS-tagged name (Editor [Win] vs Editor [macOS]).
There is no screen-style re-attach handle on Windows; manage the spawn from Task Manager, the minimized console / wt tab, or by the <DISPLAY_NAME> in the mobile picker.
Optional/advanced: fully hidden window. Use -WindowStyle Hidden only if you have already confirmed that a windowless console launches claude correctly on this machine. It is windowless (no taskbar entry at all), but a fully hidden process does not always get the interactive console/PTY claude needs — if a given claude build refuses to start without a TTY when hidden, it dies silently on boot and never reaches the mobile picker. If you've verified it works here, take the fallback branch above and swap -WindowStyle Minimized for -WindowStyle Hidden:
# Reuse $encoded from § 4 Win — same launcher, hidden window instead of minimized.
Start-Process -FilePath 'powershell.exe' -WindowStyle Hidden -ArgumentList "-NoExit -NoProfile -EncodedCommand $encoded"
If a Hidden spawn does not register in the picker (see § Verifying), fall back to the Minimized default above.
5 — Confirm and report
Confirm the spawn (see § Verifying), then tell the user briefly:
- which directory + picker label (e.g.
Editor [macOS] or Editor [Win]) to look for in the mobile picker;
- that it should appear within a few seconds;
- how to manage it:
- Unix: re-attach on the desktop with
screen -r <HANDLE>, kill with screen -S <HANDLE> -X quit.
- Windows: there is no re-attach handle; close it from the mobile picker, or end the
claude/node process in Task Manager. (If launched minimized, just close that window.)
On the --remote-control flag: passing --remote-control <name> at startup is equivalent to typing /remote-control as the first slash command in a fresh session. The user's remoteControlAtStartup setting (in ~/.claude/settings.json) controls whether remote control activates automatically without the flag; the explicit flag in this skill makes the behavior the same regardless of that setting, and supplies the picker <name>.
Alternative: visible window
Use only when the user explicitly wants a desktop window instead of a background session.
macOS / Linux — visible Terminal
NOT remote-safe: it launches a separate Terminal.app, which needs its own Full Disk Access grant (Known limitation 1) for any dir under ~/Desktop/~/Documents/etc. Use open, NOT osascript/AppleScript — AppleScript needs Automation (Apple Events) TCC permission the Claude Code process usually lacks, and the call hangs forever on an invisible prompt (AppleEvent timed out -1712). open uses LaunchServices and needs no such permission.
LAUNCHER="$(mktemp -t rc-new).command"
printf '#!/bin/bash\nexport PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"\ncd %q && exec claude --remote-control %q\n' "<ABSOLUTE_DIR>" "<DISPLAY_NAME>" > "$LAUNCHER"
chmod +x "$LAUNCHER"
open -a Terminal "$LAUNCHER"
%q safely quotes path and name. The launcher file is disposable. (iTerm2: open -a iTerm "$LAUNCHER".)
Windows — visible Windows Terminal / console
A visible window (handy for debugging a spawn that won't register):
# Windows Terminal (if installed): visible tab in the target dir. Reuse $encoded from § 4 Win —
# putting claude + the name directly in wt's tail splits "Editor [Win]" (wt strips tail quotes).
wt -w 0 nt --title '<DISPLAY_NAME>' -d '<ABSOLUTE_DIR>' powershell.exe -NoExit -NoProfile -EncodedCommand $encoded
# Or a plain visible console — ArgumentList as ONE pre-quoted string, never an array:
Start-Process -WindowStyle Normal -WorkingDirectory '<ABSOLUTE_DIR>' `
-FilePath (Get-Command claude).Source -ArgumentList "--remote-control `"<DISPLAY_NAME>`""
(The platform tag from 3.1 applies here too — pass <DISPLAY_NAME> to --remote-control.)
Known limitation 1: macOS TCC — Unix § Alternative Terminal launcher only
The default screen launcher (and all Windows paths) do NOT hit this. Applies only when a macOS user opts into the visible Terminal window.
If the target dir is under a TCC-protected location (~/Desktop, ~/Documents, ~/Downloads, iCloud Drive, external volumes), a freshly-launched Terminal.app lacking "Files and Folders"/Full Disk Access cannot read it, and claude dies immediately with error: An internal error occurred (EPERM) — the window flashes and closes, nothing reaches the picker. Invisible from inside a running Claude Code session, because that process already has Full Disk Access.
Fix (one-time, user action): System Settings → Privacy & Security → Full Disk Access → enable Terminal (or iTerm). TCC is keyed to the app bundle, so one grant covers all future windows. After granting, fully quit and reopen Terminal. To confirm the cause: spawn the same command in /tmp (not TCC-protected) — if it works there but not in the target dir, it's this. Or just use the default screen launcher, which avoids the whole issue.
Known limitation 2: workspace trust prompt (now auto-handled, both platforms)
The first time Claude Code runs in a given directory, it shows an interactive workspace trust prompt before activating any features (remote control included). A session spawned in a not-yet-trusted directory hangs at that prompt and never appears in the mobile picker.
Step 3.5 pre-seeds the trust flag (hasTrustDialogAccepted in ~/.claude.json / %USERPROFILE%\.claude.json) before spawning, so this prompt no longer fires for remote spawns on either platform. Manual fallback if the pre-seed is ever skipped: open Claude Code on the desktop in the new project once, accept the trust prompt, then close.
Verifying a spawn worked
claude rewrites its process title to a bare claude (flags hidden), so grepping for --remote-control will NOT find it.
Unix:
- Confirm the screen session is live:
screen -ls | grep <HANDLE> should show it as (Detached).
- Count claude processes before and after (
ps -eo pid,args | grep -iw claude | grep -vE 'grep|Claude.app|Claude Helper' | wc -l) — the count should rise and stay up after a few seconds. (claude spawns children, so it can rise by more than one — fine; what matters is it doesn't drop back.)
If the screen session vanishes or the count drops back, the session died on boot — almost always claude not on the launcher's PATH (fix the inner export PATH), or, for the Alternative launcher only, TCC limitation 1.
Windows (PowerShell):
- Count claude/node processes before and after the spawn:
(Get-Process -Name claude,node -ErrorAction SilentlyContinue | Measure-Object).Count
The count should rise and stay up after a few seconds. (The claude launcher is often a node process, so check both names.)
- If it does not rise or drops back, the spawn died on boot — almost always
claude not on PATH for Start-Process (resolve (Get-Command claude).Source and pass it as -FilePath), or the hidden-window TTY weak point (retry with -WindowStyle Minimized per § 4 Win).
- Verify the name survived quoting — the failure mode that ships a working session under the WRONG name. Windows WMI captures
CommandLine at process creation, so it still shows the real flags even though claude rewrites its process title:
Get-CimInstance Win32_Process -Filter "Name='claude.exe' OR Name='node.exe'" |
Where-Object { $_.CommandLine -match 'remote-control' } |
Select-Object ProcessId, Name, CommandLine
The new process must show --remote-control "Editor [Win]" — name quoted, ONE argument. If you see --remote-control Editor [Win] unquoted, the name split: the session boots but registers in the picker as Editor with [Win] as its first prompt. Kill it and respawn via the § 4 Win -EncodedCommand path.
Do NOT
- Run
claude in the same terminal as the current session (that clobbers the active session).
- Run the bash/
screen blocks on Windows, or the PowerShell blocks on Unix — branch on OS first (Step 0).
- On Windows: pass
-ArgumentList an ARRAY whose elements contain spaces (PS 5.1 joins them unquoted), or route the display name through powershell -Command / wt's command tail (both re-join argv unquoted). The name must travel inside the § 4 Win -EncodedCommand payload — that's the postmortem-verified path.
- Pick a default project when no directory is given. Use cwd.
- Skip the existence check on the target directory.
- Reuse a handle that's already attached to a live session. On Unix,
screen -S <HANDLE> must be unique — if the project already has a live session on this machine, append a counter (rc-Editor-macOS-2). The platform tag disambiguates across machines, but two sessions for the same project on the same machine still need a counter (apply the same counter to <DISPLAY_NAME> so the picker shows it too, e.g. Editor [macOS] 2).