[BUG] Desktop SSH: slash palette omits all remote custom skills in a fresh session — ccd-cli is spawned lazily on first message, so its initialize handshake never runs
Summary
In a desktop SSH (remote) session, the / slash-command palette lists only built-in commands plus claude.ai cloud-synced skills. None of the remote host's custom skills (~/.claude/skills) appear — until you send any message. After one exchange (even just "hi"), every custom skill appears and works normally.
Root cause appears to be that the app spawns the remote ccd-cli process lazily, on the first message, rather than at session open. The palette is populated from that process's initialize handshake, so in a fresh chat the handshake has not happened yet and the palette falls back to whatever it already has.
Environment
- Client: Claude Desktop on macOS (Darwin 25.5.0), Code tab
- Remote host: macOS Mac Mini over SSH, 40 custom skills in
~/.claude/skills - Remote CLI:
~/.claude/remote/ccd-cli/2.1.219 - Also reproduced on 2.1.215 / 2.1.217, and with terminal CLI 2.1.220 on the same host
Reproduction
- Start a desktop session against an SSH remote that has custom skills in
~/.claude/skills. - Without sending anything, type
/(or a prefix like/lan) in the prompt box. - Observe: only built-ins and cloud-synced skills are offered. No custom skills.
- Send any message (
hi) and wait for a reply. - Type the same
/lanagain —landnow appears and works.
Evidence
1. Lazy spawn — remote server log (~/.claude/remote/run/<id>/remote-server.log):
Claude remote server listening on .../rpc.sock
2026/07/30 18:40:59 [Server] New connection from: <- session opens
2026/07/30 18:41:24 [process.Manager] Process ... started, PID=36292,
command=/Users/kipholt/.claude/remote/ccd-cli/2.1.219
The ccd-cli process does not start at 18:40:59 when the session connects. It starts at 18:41:24, when the first message is sent — a 25s gap in which the palette has no custom-skill data.
2. The CLI itself returns the full list correctly. Driving the same binary the app launches, with the app's own wire protocol, from the same project directory:
cd <project> && { printf '%s\n' '{"type":"control_request","request_id":"p","request":{"subtype":"initialize"}}'; sleep 10; } \
| ~/.claude/remote/ccd-cli/2.1.219 --output-format stream-json --input-format stream-json \
--verbose --model <model> --permission-prompt-tool stdio 2>/dev/null
Returns 88 commands, including all 40 user skills correctly tagged (user) — land, ship, handoff, etc. So enumeration on the host is fine; the data simply is not available to the palette yet in a fresh session.
3. Fallback matching confirms the palette is using a different, smaller set. In a fresh session:
- typing
/lanofferedusage— matching "Show plan usage and rate limits" - typing
/askofferedschedule— matching "Create a scheduled task"
Both are fuzzy matches against descriptions in the built-in/cloud set. schedule is not present in the CLI's 88-command list at all, confirming the palette's source in a fresh session is not the remote CLI.
Note this also affects cloud-synced skills: a cloud skill named ask-user-question-tool was enabled on the account, and /ask still did not offer it in a fresh session.
Expected
Custom skills from the remote host should be available in the slash palette as soon as the session is ready, without requiring a first message. The docs state that an SSH session reads ~/.claude/skills from the remote host's home directory, with no mention of a first-message requirement.
Actual
They are absent until the first message triggers the ccd-cli spawn and its initialize handshake.
Impact
Every new chat silently appears to have lost all custom skills. It reads as a broken installation rather than a timing issue, and sends users debugging files, symlinks, and MCP config that were never at fault.
Possible fixes
- Spawn
ccd-cli(or at least perform theinitializehandshake) at session open rather than on first message. - Or cache the last known command list per host/project and show it immediately, refreshing after the handshake.
Workaround
Send any message in a fresh chat before using a slash command.