# Rc New

> 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.

- Skill: `phuryn/rc-new` (Agent Skill)
- Install (CLI): `npx skillmds@latest add phuryn/rc-new`
- Raw SKILL.md: https://api.skillmd.com/api/skills/phuryn/rc-new/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: phuryn (https://skillmd.com/u/phuryn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/phuryn/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)

1. Read `ARGUMENTS`. If non-empty, treat as the target directory. If empty, use the current working directory.
2. 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>'`
3. 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:

```bash
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:

```powershell
$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:**

```bash
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):

```powershell
$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:

```bash
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:

```powershell
# <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`:

```powershell
# 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.

```bash
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):

```powershell
# 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:**
1. Confirm the screen session is live: `screen -ls | grep <HANDLE>` should show it as `(Detached)`.
2. 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):**
1. Count claude/node processes before and after the spawn:
   ```powershell
   (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.)
2. 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).
3. **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:
   ```powershell
   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`).

