Skip to content

Infrastructure

Claude Code Launchers

Two macOS .app bundles sit in ~/Applications/. Both open VS Code on ~/apps, but each spawns Claude in a different surface. Both scripts live in the cc repo so they sync across machines.

IconNameSpawns Claude inScript
Claude Code launcher iconClaude CodeTerminal tab inside VS Code (runs claude CLI)~/apps/cc/claude-code-launcher.sh
CC Extension launcher iconCC ExtensionVS Code editor tab via the official Claude Code extension~/apps/cc/cc-extension-launcher.sh

Both launchers follow the same three-stage pattern:

  1. Detect — AppleScript checks if a VS Code window with a matching title (claude-session for terminal, cc-extension-session for extension) is already open. Returns reuse if found, new if not.
  2. Open or reuse — On new, write a temporary .code-workspace file pointing at ~/apps, then launch VS Code with --new-window. On reuse, raise the existing window via AXRaise.
  3. Trigger Claude — Run an AppleScript sequence to open Claude in the right surface (terminal or editor tab).

The .app bundles in ~/Applications/ are thin wrappers — Contents/MacOS/launcher is a one-line shell script that calls into the cc repo. This keeps the actual logic synced across all four machines via GitHub.

This is the part that took some debugging. The CC extension has different behavior at cold start vs warm start, so the launcher script branches:

First click (cold start, REUSE="new"):

  • VS Code opens fresh
  • The Claude Code extension auto-opens a Claude tab on activation
  • Script runs ONLY the side bar close — does NOT call Claude: Open New Tab (would open a duplicate)

Second click (warm start, REUSE="reuse"):

  • Existing window is raised
  • Extension does not auto-open anything
  • Script runs Claude: Open New Tab to add a fresh Claude tab, then closes the side bar

If both paths ran the same AppleScript, the first click would open two Claude tabs.

Both scripts use the Command Palette (Cmd+Shift+P) rather than direct keyboard shortcuts because keybindings are less reliable when VS Code is mid-startup.

CC Extension first click:

keystroke "p" using {command down, shift down}
keystroke "View: Close Secondary Side Bar"
keystroke return

CC Extension second click:

keystroke "p" using {command down, shift down}
keystroke "Claude: Open New Tab"
keystroke return
delay 2
keystroke "p" using {command down, shift down}
keystroke "View: Close Secondary Side Bar"
keystroke return

The side bar close exists because GitHub Copilot Chat keeps reopening the secondary side bar.

Both launchers append to ~/.claude-launcher.log:

Terminal window
tail -50 ~/.claude-launcher.log

Each invocation writes a header line (=== <date> === or === <date> [CC-EXT] ===), the detection result (reuse vs new), and the workspace path if a new one was created.

PathRole
~/apps/cc/claude-code-launcher.shTerminal launcher script
~/apps/cc/cc-extension-launcher.shExtension launcher script
~/Applications/Claude Code.appTerminal launcher .app bundle
~/Applications/CC Extension.appExtension launcher .app bundle
~/apps/cc/setup-launcher-app.shBootstrap script that builds the .app bundles
~/.claude-launcher.logCombined log for both launchers

If a launcher breaks, the known-good commit is 6223158 in the cc repo (Build 33). Diff against it to see what drifted.

Terminal window
cd ~/apps/cc
git diff 6223158 -- claude-code-launcher.sh cc-extension-launcher.sh