name: agentic-ecosystem-deployment description: Bootstrap the complete agentic ecosystem (Claude Code, Codex, vscode-shims, agent-box) on a remote machine. Use when user asks to install ecosystem, setup remote agent, bootstrap agent box, deploy to remote, or install claude/codex on another machine.
Agentic Ecosystem Remote Bootstrap
Complete guide for deploying the agentic development ecosystem to a remote machine, including Claude Code, Codex CLI, vscode-shims, and supporting infrastructure.
Quick Start Checklist
# 1. Verify SSH access
ssh -p <PORT> <USER>@<HOST> "uname -a"
# 2. Install prerequisites (Node.js, Cargo)
ssh -p <PORT> <HOST> "/opt/homebrew/bin/brew install node"
# Cargo usually pre-installed, verify with: cargo --version
# 3. Deploy Claude Code + Codex (see detailed steps below)
# 4. Create symlinks
ssh -p <PORT> <HOST> "mkdir -p ~/symlinks && \
ln -sf ~/swe/claude-code-X.Y.Z/cli.js ~/symlinks/claude && \
ln -sf ~/swe/codex.X.Y.Z/codex-rs/target/release/codex ~/symlinks/codex"
# 5. Deploy auth (see authentication section)
# 6. Smoke test
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
~/symlinks/claude -p 'What is 2 + 2?'"
ssh -p <PORT> <HOST> "~/symlinks/codex exec --skip-git-repo-check 'What is 2 + 2?'"
Critical Knowledge (Lessons Learned)
⚠️ IMPORTANT: Check Remote System Info First
Before any deployment, gather system details:
# Check OS, architecture, memory
ssh -p <PORT> <HOST> "uname -a && sysctl hw.memsize 2>/dev/null | awk '{print \$2/1024/1024/1024 \" GB\"}'"
# Check for Node.js
ssh -p <PORT> <HOST> "which node && node --version || echo 'Node.js NOT installed'"
# Check for Cargo
ssh -p <PORT> <HOST> "which cargo && cargo --version || echo 'Cargo NOT installed'"
# Check for Homebrew
ssh -p <PORT> <HOST> "which brew || ls /opt/homebrew/bin/brew"
⚠️ IMPORTANT: Trace Symlinks to Find Correct Versions
Always check ~/symlinks/ on LOCAL machine to determine which versions to deploy:
# On LOCAL machine - check what versions to deploy
ls -la ~/symlinks/claude ~/symlinks/codex
# Example output:
# claude -> /Users/sotola/swe/claude-code-2.1.15/cli.js
# codex -> /Users/sotola/swe/codex.0.88.0/codex-rs/target/release/codex
NEVER assume version numbers - always trace the symlinks!
⚠️ IMPORTANT: Exclude Large Files When Transferring
When cloning projects for transfer, ALWAYS exclude:
| Exclude | Reason | Typical Size |
|---|---|---|
node_modules/ |
Reinstall on target | 50-500 MB |
.git/ |
Not needed for deployment | 10-100 MB |
target/ (Rust) |
Rebuild on target | 500+ MB |
.electron-dev/ |
Electron binaries | 150+ MB |
__pycache__/ |
Python cache | Small but unnecessary |
rsync exclude pattern:
rsync -av --exclude='.git' --exclude='node_modules' --exclude='target' \
--exclude='.electron-dev' --exclude='__pycache__' --exclude='*.pyc' \
<source>/ /tmp/<project>-clean/
⚠️ IMPORTANT: PATH Issues with SSH Non-Interactive Shell
SSH commands run in non-interactive shells that DON'T load ~/.zshrc. Always export PATH:
# WRONG - node not found
ssh <HOST> "node --version"
# CORRECT - explicitly set PATH
ssh <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && node --version"
CRITICAL for shim servers: When starting vscode-shims remotely, the Python process inherits the limited SSH PATH. If /opt/homebrew/bin is not included, the shim will fail to spawn Claude CLI with error: [Errno 2] No such file or directory: 'node'. This error appears in the webview UI, not in SSH output.
Always start shims with:
ssh <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
cd ~/swe/vscode-shims && \
nohup ~/anaconda3/bin/python src/claude/server.py > /tmp/claude-shim.log 2>&1 &"
⚠️ IMPORTANT: python vs python3 on macOS
macOS has python3 but often no python alias:
# WRONG - command not found
ssh <HOST> "python script.py"
# CORRECT - use python3
ssh <HOST> "python3 script.py"
Fix launcher scripts that use python to use python3 instead.
⚠️ IMPORTANT: npm install Needs AUTHORIZED=1 for Claude Code
Claude Code package.json has a protection script that blocks direct installs:
# WRONG - fails with "Direct publishing is not allowed"
npm install
# CORRECT - set AUTHORIZED flag
AUTHORIZED=1 npm install
⚠️ IMPORTANT: Hardcoded Paths in server.py
The vscode-shims server.py may have hardcoded fallback paths:
# server.py line ~940 - PROBLEMATIC!
cli_path = os.environ.get("CLAUDE_CODE_CLI") or \
os.path.expanduser("~/swe/claude-code-2.1.12/cli.js") # <-- HARDCODED!
Fix: Always set CLAUDE_CODE_CLI in .env or use --cli flag:
# In .env
CLAUDE_CODE_CLI=~/symlinks/claude
# Or via command line
python3 server.py --cli ~/symlinks/claude
⚠️ IMPORTANT: SHIM_DEFAULT_CWD Directory Must Exist
The shim server requires the working directory to exist:
# Error: Failed to spawn CLI: [Errno 2] No such file or directory: '/Users/sotola/agent-home'
Fix: Create the directory or sync from local:
# Simple fix
ssh <HOST> "mkdir -p ~/agent-home"
# Better - sync structure with config files
rsync -av --include='*/' --include='*.md' \
--exclude='*' -e "ssh -p <PORT>" ~/agent-home/ <HOST>:~/agent-home/
⚠️ IMPORTANT: Network Binding - NEVER Use 0.0.0.0
When configuring SHIM_HOST for network access:
# DANGEROUS - exposes to ALL networks including internet!
SHIM_HOST=0.0.0.0
# CORRECT - bind to specific LAN interface
SHIM_HOST=192.168.1.9 # Your machine's LAN IP
Security levels:
127.0.0.1= localhost only (most secure, requires SSH tunnel)192.168.1.X= LAN interface only (secure for trusted network)= NEVER USE - exposes to all networks0.0.0.0
⚠️ IMPORTANT: Codex Shim Needs Python Dependencies
The Codex shim server requires requests module:
# Error: ModuleNotFoundError: No module named 'requests'
# Fix
pip3 install requests
Component Deployment
1. Claude Code Deployment
On LOCAL machine:
# 1. Check which version to deploy (trace symlink)
readlink ~/symlinks/claude
# Output: /Users/sotola/swe/claude-code-2.1.15/cli.js
# 2. Clone to /tmp excluding node_modules and .git
cp -r ~/swe/claude-code-2.1.15 /tmp/claude-code-2.1.15
cd /tmp/claude-code-2.1.15
rm -rf node_modules .git soto_docs soto_reqs ai
# 3. Check size (should be ~70-80 MB without node_modules)
du -sh /tmp/claude-code-2.1.15
# 4. Zip for transfer
cd /tmp && zip -r claude-code-2.1.15.zip claude-code-2.1.15
# 5. Transfer to remote
scp -P <PORT> /tmp/claude-code-2.1.15.zip <HOST>:/tmp/
On REMOTE machine:
# 1. Extract
ssh -p <PORT> <HOST> "cd /tmp && unzip -q claude-code-2.1.15.zip && \
mkdir -p ~/swe && mv claude-code-2.1.15 ~/swe/"
# 2. Install dependencies (note AUTHORIZED=1)
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
cd ~/swe/claude-code-2.1.15 && AUTHORIZED=1 npm install"
# 3. Create symlink
ssh -p <PORT> <HOST> "mkdir -p ~/symlinks && \
ln -sf ~/swe/claude-code-2.1.15/cli.js ~/symlinks/claude && \
ln -sf ~/swe/claude-code-2.1.15 ~/symlinks/claude-code-2.1.15"
# 4. Verify
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
~/symlinks/claude --version"
# Expected: 2.1.15 (Claude Code)
2. Codex Deployment
On LOCAL machine:
# 1. Check which version to deploy (trace symlink)
readlink ~/symlinks/codex
# Output: /Users/sotola/swe/codex.0.88.0/codex-rs/target/release/codex
# 2. Clone excluding target, node_modules, .git
cp -r ~/swe/codex.0.88.0 /tmp/codex.0.88.0
cd /tmp/codex.0.88.0
rm -rf codex-rs/target node_modules .git soto_docs
# 3. Check size (should be ~15-20 MB)
du -sh /tmp/codex.0.88.0
# 4. Zip for transfer
cd /tmp && zip -r codex.0.88.0.zip codex.0.88.0
# 5. Transfer to remote
scp -P <PORT> /tmp/codex.0.88.0.zip <HOST>:/tmp/
On REMOTE machine:
# 1. Extract
ssh -p <PORT> <HOST> "cd /tmp && unzip -q codex.0.88.0.zip && \
mkdir -p ~/swe && mv codex.0.88.0 ~/swe/"
# 2. Build with cargo (takes several minutes)
ssh -p <PORT> <HOST> "cd ~/swe/codex.0.88.0/codex-rs && cargo build --release"
# 3. Create symlink
ssh -p <PORT> <HOST> "mkdir -p ~/symlinks && \
ln -sf ~/swe/codex.0.88.0/codex-rs/target/release/codex ~/symlinks/codex"
# 4. Verify
ssh -p <PORT> <HOST> "~/symlinks/codex --version"
# Expected: codex-cli 0.88.0
3. vscode-shims Deployment
On LOCAL machine:
# 1. Clone excluding unnecessary files
rsync -av --exclude='.git' --exclude='node_modules' --exclude='.playwright-mcp' \
--exclude='__pycache__' --exclude='*.pyc' \
~/swe/vscode-shims/ /tmp/vscode-shims-clean/
# 2. Check size
du -sh /tmp/vscode-shims-clean # Should be ~70-80 MB
# 3. Transfer to remote
rsync -avz --progress -e "ssh -p <PORT>" \
/tmp/vscode-shims-clean/ <HOST>:~/swe/vscode-shims/
# 4. Clean up local temp
rm -rf /tmp/vscode-shims-clean
On REMOTE machine - Configure .env:
ssh -p <PORT> <HOST> "cat >> ~/swe/vscode-shims/.env << 'EOF'
# CLI Paths (use symlinks for version flexibility)
CLAUDE_CODE_CLI=~/symlinks/claude
CODEX_CLI=~/symlinks/codex
# Network Access Configuration
# 127.0.0.1 = localhost only (requires SSH tunnel)
# 192.168.1.X = LAN access (replace with your machine's LAN IP)
SHIM_HOST=127.0.0.1
CODEX_SHIM_HOST=127.0.0.1
EOF"
Fix launcher script (python -> python3):
ssh -p <PORT> <HOST> "sed -i '' 's/python src/python3 src/g' \
~/swe/vscode-shims/launchers/launch_server.sh"
4. agent-home Directory
# Sync directory structure with CLAUDE.md (no large data files)
rsync -av --include='*/' --include='CLAUDE.md' --include='AGENTS.md' \
--include='.mcp.json' --exclude='*' \
-e "ssh -p <PORT>" ~/agent-home/ <HOST>:~/agent-home/
5. Telemetry Projects (Optional)
# Transfer each project
for project in codex_sqlite claude_sqlite agent_dump; do
rsync -avz --progress -e "ssh -p <PORT>" \
~/swe/telemetry_projects/$project <HOST>:~/swe/telemetry_projects/
done
6. Agent Box (Optional)
# Clone excluding large files
rsync -av --exclude='.git' --exclude='node_modules' --exclude='.electron-dev' \
--exclude='__pycache__' --exclude='*.pyc' \
~/AgenticProjects/agent-box-v1/ /tmp/agent-box-v1-clean/
# Transfer
rsync -avz --progress -e "ssh -p <PORT>" \
/tmp/agent-box-v1-clean/ <HOST>:~/AgenticProjects/agent-box-v1/
# Clean up
rm -rf /tmp/agent-box-v1-clean
Authentication Deployment
Claude Code Auth
See skill: claude-usage-meter for full OAuth refresh workflow.
Automated upload to multiple remotes (recommended):
# Upload to all configured remotes (cm3u, cm2, etc.)
python ~/.claude/skills/claude-usage-meter/scripts/upload_credentials_to_remotes.py
# Upload to specific remotes
python ~/.claude/skills/claude-usage-meter/scripts/upload_credentials_to_remotes.py cm3u cm2
The upload script:
- Reads SSH aliases from
~/.zshrc - Uploads
~/.claude/.credentials.jsonto remote~/.claude/ - Sets correct permissions (600)
- Tests each remote after upload
- Shows success/failure summary
Manual upload (if script unavailable):
# Copy credentials to remote
scp -P <PORT> ~/.claude/.credentials.json <HOST>:~/.claude/.credentials.json
# Via SSH alias with pipe
cat ~/.claude/.credentials.json | cm3u 'cat > ~/.claude/.credentials.json && chmod 600 ~/.claude/.credentials.json'
Codex Auth
See skill: codex-remote-auth for details.
# Simple copy
scp -P <PORT> ~/.codex/auth.json <HOST>:~/.codex/auth.json
Smoke Test Commands
Test Claude Code CLI
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
~/symlinks/claude -p 'What is 2 + 2?'"
# Expected output: "2 + 2 = 4" or similar
# Auth error is OK - confirms binary works
Test Codex CLI
ssh -p <PORT> <HOST> \
"~/symlinks/codex exec --skip-git-repo-check 'What is 2 + 2?'"
# Expected: Shows "codex-cli X.Y.Z" header, then response or auth error
# Auth error is OK - confirms binary works
Check Versions
ssh -p <PORT> <HOST> \
"~/symlinks/codex --version && echo '---' && \
export PATH=\"/opt/homebrew/bin:\$PATH\" && ~/symlinks/claude --version"
# Expected:
# codex-cli 0.88.0
# ---
# 2.1.15 (Claude Code)
Test Shim Server
# Start server (CRITICAL: export PATH for node)
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
cd ~/swe/vscode-shims && \
nohup ~/anaconda3/bin/python src/claude/server.py > /tmp/claude-shim.log 2>&1 &"
# Wait and check
sleep 3
ssh -p <PORT> <HOST> "lsof -i :8787"
# Curl test
ssh -p <PORT> <HOST> "curl -s http://localhost:8787/ | head -20"
# Expected: HTML with "Claude Webview Shim"
⚠️ CRITICAL: Always export PATH="/opt/homebrew/bin:$PATH" before starting shims, otherwise node will not be found when spawning CLI.
SSH Tunnel for Local Access
# Create tunnel (run on LOCAL machine)
ssh -p <PORT> -f -N -L 18787:localhost:8787 <USER>@<HOST>
# Access at http://localhost:18787
Port Mapping for SSH Tunnels
⚠️ CRITICAL: Reserve Local Ports for Local Services
NEVER tunnel remote services to ports already used locally. For example:
- Port
8787is used by local Claude vscode-shim - Port
9288is used by local Codex vscode-shim - Port
8037is used by local Agent HQ
If you tunnel remote:8787 to local:8787, you'll kill your local service!
Port Mapping Convention
Use +20000 offset for remote tunnels:
| Service | Local Port | Remote Tunnel Port |
|---|---|---|
| Claude shim | 8787 | 28787 |
| Codex shim | 9288 | 29288 |
| Agent HQ | 8037 | 28037 |
Using the Tunnel Launcher Script
The launch_ssh_tunnel_to_m2_tmux.py script in ~/swe/vscode-shims/launchers/ supports explicit port mapping:
# Start tunnel with proper port mapping (avoids local port conflicts)
cd ~/swe/vscode-shims && \
python launchers/launch_ssh_tunnel_to_m2_tmux.py --verbose \
--map 28787:8787,29288:9288,28037:8037
This creates:
localhost:28787→remote:8787(Claude shim)localhost:29288→remote:9288(Codex shim)localhost:28037→remote:8037(Agent HQ)
Verifying Tunnel is Working
# Check tunnel ports are listening locally
lsof -nP -iTCP:28787 -sTCP:LISTEN
lsof -nP -iTCP:29288 -sTCP:LISTEN
lsof -nP -iTCP:28037 -sTCP:LISTEN
# Curl test through tunnel
curl -sS -m 3 http://127.0.0.1:28787/ | head -5
Tmux Session Management
The tunnel runs in tmux session ssh-tunnel-to-m2:
# Attach to see tunnel status/logs
tmux attach -t ssh-tunnel-to-m2
# List sessions
tmux list-sessions | grep tunnel
# Restart tunnel (re-run the launcher)
python launchers/launch_ssh_tunnel_to_m2_tmux.py --verbose --map 28787:8787,29288:9288,28037:8037
Post-Deployment Smoke Testing
1. Start Local Services First
Before testing tunnels, ensure local services are running:
# Start local vscode-shim (claims port 8787)
cd ~/swe/vscode-shims && python launchers/launch_server_tmux.py
# Verify local service
curl -sS http://127.0.0.1:8787/ | head -3
2. Start SSH Tunnel with Port Mapping
cd ~/swe/vscode-shims && \
python launchers/launch_ssh_tunnel_to_m2_tmux.py --verbose \
--map 28787:8787,29288:9288,28037:8037
3. Verify Both Local and Remote Access
# Local service (direct)
curl -sS http://127.0.0.1:8787/ | head -3
# Expected: HTML with "Claude Webview Shim"
# Remote service (via tunnel)
curl -sS http://127.0.0.1:28787/ | head -3
# Expected: HTML with "Claude Webview Shim" (from remote)
4. Browser Access URLs
| Service | Local URL | Remote (via tunnel) |
|---|---|---|
| Claude shim | http://localhost:8787 | http://localhost:28787 |
| Codex shim | http://localhost:9288 | http://localhost:29288 |
| Agent HQ | http://localhost:8037 | http://localhost:28037 |
5. Troubleshooting Tunnel Issues
Port not listening after starting tunnel:
# Check tmux session for errors
tmux capture-pane -t ssh-tunnel-to-m2 -p -S -30 | tail -20
"Address already in use" error:
# Find what's using the port
lsof -nP -iTCP:28787 -sTCP:LISTEN
# Kill it if necessary (be careful!)
kill <PID>
Remote service not running:
# Check if remote port is listening
ssh -p <PORT> <HOST> "lsof -nP -iTCP:8787 -sTCP:LISTEN"
# Start remote service if needed
ssh -p <PORT> <HOST> "cd ~/swe/vscode-shims && python launchers/launch_server_tmux.py"
Troubleshooting
"node: command not found"
Cause: Node.js not installed or not in PATH for non-interactive SSH.
Fix:
# Install Node.js
ssh <HOST> "/opt/homebrew/bin/brew install node"
# Always export PATH in SSH commands
ssh <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && node --version"
"Failed to spawn CLI: [Errno 2] No such file or directory: 'node'"
Cause: Shim server can't find node executable because /opt/homebrew/bin is not in PATH when the Python process starts. This error appears in the webview UI when trying to start a Claude CLI session.
Symptoms:
- Webview loads successfully (http://localhost:28787 shows interface)
- Red error banner: "Failed to spawn CLI: [Errno 2] No such file or directory: 'node'"
- Shim HTTP logs show successful requests but no CLI spawn
Fix: Restart shim with PATH exported:
# WRONG - Python process inherits limited PATH
ssh <HOST> "cd ~/swe/vscode-shims && nohup python src/claude/server.py > /tmp/claude-shim.log 2>&1 &"
# CORRECT - Export PATH before starting shim
ssh <HOST> "pkill -f 'python.*claude.*server.py' && sleep 2 && \
cd ~/swe/vscode-shims && \
export PATH=\"/opt/homebrew/bin:\$PATH\" && \
nohup ~/anaconda3/bin/python src/claude/server.py > /tmp/claude-shim.log 2>&1 &"
Prevention: Always include PATH export when starting shims remotely.
Critical for: cm2, cm3u, and any remote machine where /opt/homebrew/bin is not in default PATH.
"python: command not found"
Cause: macOS has python3 but no python alias.
Fix:
# Use python3 explicitly
ssh <HOST> "python3 --version"
# Fix scripts that use 'python'
sed -i '' 's/python /python3 /g' script.sh
"Direct publishing is not allowed" (npm install)
Cause: Claude Code package.json has protection script.
Fix:
AUTHORIZED=1 npm install
"CLI not found: /Users/sotola/swe/claude-code-2.1.12/cli.js"
Cause: Hardcoded path in server.py or missing env var.
Fix:
# Set in .env
echo "CLAUDE_CODE_CLI=~/symlinks/claude" >> ~/swe/vscode-shims/.env
# Or use --cli flag
python3 server.py --cli ~/symlinks/claude
"No such file or directory: '/Users/sotola/agent-home'"
Cause: SHIM_DEFAULT_CWD points to non-existent directory.
Fix:
mkdir -p ~/agent-home
"ModuleNotFoundError: No module named 'requests'"
Cause: Python dependencies not installed.
Fix:
pip3 install requests
Shim server not accessible from LAN
Cause: Bound to localhost (127.0.0.1).
Fix: Set SHIM_HOST to your LAN IP in .env:
SHIM_HOST=192.168.1.9 # Replace with actual LAN IP
Security note: NEVER use 0.0.0.0 - it exposes to ALL networks.
Auth errors after deployment
Cause: Credentials not copied or expired.
Fix (Claude Code):
# Automated upload to all remotes (recommended)
python ~/.claude/skills/claude-usage-meter/scripts/upload_credentials_to_remotes.py
# Manual upload via SCP
scp -P <PORT> ~/.claude/.credentials.json <HOST>:~/.claude/.credentials.json
# Manual upload via SSH alias
cat ~/.claude/.credentials.json | cm3u 'cat > ~/.claude/.credentials.json && chmod 600 ~/.claude/.credentials.json'
Fix (Codex):
# Copy auth
scp -P <PORT> ~/.codex/auth.json <HOST>:~/.codex/auth.json
🚨 CRITICAL: crypto.randomUUID Failure on HTTP/IP Access
If remote webview messages fail silently, THIS IS LIKELY THE CAUSE.
Symptoms
- UI works, shows "Agent running"
POST /api/sendreturns 200 OK- Traffic logs show malformed messages (
"command": "sendMessage") instead ofio_message - Agent HQ shows brief flashing activity but no text appears
Root Cause
crypto.randomUUID() is unavailable in insecure contexts (HTTP over IP like http://192.168.1.9:8787). Modern browsers disable it outside HTTPS/localhost.
When randomUUID() fails, the frontend falls back to a legacy payload format the server doesn't recognize → messages silently dropped.
Fix: Inject Polyfill into vscode-shim.js
Add this at the top of src/claude/public/vscode-shim.js on the remote server:
/* Polyfill for insecure contexts (HTTP over IP) */
if (!window.crypto) window.crypto = {};
if (!window.crypto.randomUUID) {
window.crypto.randomUUID = function () {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
},
);
};
}
Verification
- Reload page with cache bust:
?v=new - Open console, run:
typeof crypto.randomUUID→ should return"function" - Send a test message and verify it appears in logs
⚠️ WARNING: Do NOT use page.evaluate()
Injecting via devtools/page.evaluate only fixes the running session. On page reload, the fix is lost. Always patch the file on disk.
📖 Full incident report: See references/incident_report_randomuuid.md for the complete 2026-01-24 debugging story.
⚠️ CRITICAL: Never Run Electron on Remote
On remote deployments, ONLY run the web version of Agent HQ.
Why?
- Electron apps spawn multiple background processes
- Over SSH, these become orphaned/zombie processes
- They accumulate and consume resources
- Require
pkill -9orkill -9to terminate - No benefit over web version when accessing remotely anyway
Correct Approach
# WRONG - don't run Electron on remote
npm run dev # Spawns Electron
npm run start # Spawns Electron
# CORRECT - run web version only
npm run dev:web # Web server only, access via browser
If You Already Have Zombie Electron Processes
# Find them
ssh -p <PORT> <HOST> "ps aux | grep -i electron | grep -v grep"
# Kill them
ssh -p <PORT> <HOST> "pkill -9 -f 'Electron\|electron'"
Directory Structure Reference
After complete deployment, the remote machine should have:
~/
├── symlinks/
│ ├── claude -> ~/swe/claude-code-X.Y.Z/cli.js
│ ├── claude-code-X.Y.Z -> ~/swe/claude-code-X.Y.Z
│ └── codex -> ~/swe/codex.X.Y.Z/codex-rs/target/release/codex
├── swe/
│ ├── claude-code-X.Y.Z/
│ │ ├── cli.js
│ │ ├── node_modules/
│ │ └── ...
│ ├── codex.X.Y.Z/
│ │ └── codex-rs/
│ │ └── target/release/codex
│ ├── vscode-shims/
│ │ ├── .env
│ │ ├── src/claude/server.py
│ │ └── launchers/launch_server.sh
│ └── telemetry_projects/ (optional)
├── agent-home/
│ ├── CLAUDE.md
│ └── AGENTS.md -> CLAUDE.md
├── AgenticProjects/
│ └── agent-box-v1/ (optional)
├── .claude/
│ └── .credentials.json
└── .codex/
└── auth.json
Environment Variables Reference
vscode-shims .env
| Variable | Default | Purpose |
|---|---|---|
SHIM_PORT |
8787 |
Claude shim HTTP port |
SHIM_HOST |
127.0.0.1 |
Network interface to bind (use LAN IP for network access) |
SHIM_DEFAULT_CWD |
~/agent-home |
Working directory for spawned CLI |
CLAUDE_CODE_CLI |
(hardcoded fallback) | Path to Claude Code cli.js |
CODEX_SHIM_PORT |
9288 |
Codex shim HTTP port |
CODEX_SHIM_HOST |
127.0.0.1 |
Codex shim network interface |
CODEX_CLI |
(hardcoded fallback) | Path to Codex binary |
Prerequisites Checklist
| Requirement | Check Command | Install Command |
|---|---|---|
| Node.js | node --version |
/opt/homebrew/bin/brew install node |
| npm | npm --version |
(comes with Node.js) |
| Cargo/Rust | cargo --version |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| Python 3 | python3 --version |
Usually pre-installed on macOS |
| Homebrew | brew --version |
See brew.sh |
7. ~/.claude and ~/.codex Config Deployment
Deploy user-level config (CLAUDE.md, AGENTS.md) and skills to remote.
⚠️ IMPORTANT: Use Remote-Specific Config Files
Local CLAUDE.md and AGENTS.md contain local-only settings (say notifications, mirror-and-view script, VS Code troubleshooting, etc.) that don't apply to remotes.
Use these pre-cleaned remote versions:
~/.claude/CLAUDE.md.remote→ deploy as~/.claude/CLAUDE.md~/.codex/AGENTS.md.remote→ deploy as~/.codex/AGENTS.md
Sections removed in remote versions:
- "say" command notifications (macOS audio)
- Mirror-and-view script
- Launcher Apps table
- VS Code Extension Troubleshooting
- Skill/Jutsu Archive (
~/KnowledgeBase) - Browser MCP note
Sections preserved:
- Agentic Log Files (observability is core to ecosystem)
- CLI aliases (consistency across machines)
Quick Deploy (Config Files Only)
# Deploy CLAUDE.md and AGENTS.md to remote
scp -P <PORT> ~/.claude/CLAUDE.md.remote <HOST>:~/.claude/CLAUDE.md
scp -P <PORT> ~/.codex/AGENTS.md.remote <HOST>:~/.codex/AGENTS.md
Handle Symlinks Properly (for Skills)
Before copying skills, check for symlinks in skills directories:
# Find symlinks in ~/.claude and ~/.codex
find ~/.claude -type l -ls 2>/dev/null
find ~/.codex -type l -ls 2>/dev/null
Common symlinks to handle:
~/.codex/skills/config-transparency-centralized→~/.claude/skills/...(copy actual content)~/.codex/skills/safe-port-kill→/tmp/...(skip - not portable)
Prepare and Transfer (Full Config + Skills)
# Prepare ~/.claude config (use .remote file, follow symlinks for skills)
mkdir -p /tmp/claude-config /tmp/codex-config
cp ~/.claude/CLAUDE.md.remote /tmp/claude-config/CLAUDE.md
rsync -aL ~/.claude/skills/ /tmp/claude-config/skills/
# Prepare ~/.codex config (use .remote file, handle symlinks manually for skills)
cp ~/.codex/AGENTS.md.remote /tmp/codex-config/AGENTS.md
cd ~/.codex/skills && for d in */; do
name="${d%/}"
if [ "$name" != "safe-port-kill" ]; then # Skip broken symlinks
cp -rL "$name" /tmp/codex-config/skills/
fi
done
cp ~/.codex/skills/*.md /tmp/codex-config/skills/ 2>/dev/null
# Zip and transfer
cd /tmp && zip -rq claude-config.zip claude-config && zip -rq codex-config.zip codex-config
scp -P <PORT> /tmp/claude-config.zip /tmp/codex-config.zip <HOST>:/tmp/
Deploy on Remote
ssh -p <PORT> <HOST> "cd /tmp && unzip -q claude-config.zip && unzip -q codex-config.zip && \
mkdir -p ~/.claude/skills ~/.codex/skills && \
cp /tmp/claude-config/CLAUDE.md ~/.claude/ && \
rsync -a /tmp/claude-config/skills/ ~/.claude/skills/ && \
cp /tmp/codex-config/AGENTS.md ~/.codex/ && \
rsync -a /tmp/codex-config/skills/ ~/.codex/skills/"
Initialize Git Repos (for tracking changes)
# ~/.claude git repo
ssh -p <PORT> <HOST> "cd ~/.claude && git init && \
cat > .gitignore << 'EOF'
*
!.gitignore
!CLAUDE.md
!skills/
!skills/**
EOF
git add .gitignore CLAUDE.md skills/ && \
git commit -m 'Initial commit - CLAUDE.md and skills'"
# ~/.codex git repo
ssh -p <PORT> <HOST> "cd ~/.codex && git init && \
cat > .gitignore << 'EOF'
*
!.gitignore
!AGENTS.md
!skills/
!skills/**
EOF
git add .gitignore AGENTS.md skills/ && \
git commit -m 'Initial commit - AGENTS.md and skills'"
8. Shell Aliases Deployment
Check Local Aliases
grep -E "^alias ccodex" ~/.zshrc
# Example output:
# alias ccodex='~/symlinks/codex --dangerously-bypass-approvals-and-sandbox ...'
# alias ccodex-low='~/symlinks/codex ... -c model_reasoning_effort=low ...'
# alias ccodex-instant='~/symlinks/codex ... -c model_reasoning_effort=none ...'
Update Remote ~/.zshrc
# Download remote zshrc
scp -P <PORT> <HOST>:~/.zshrc /tmp/remote-zshrc
# Check for existing aliases
grep -E "^alias ccodex" /tmp/remote-zshrc
# Edit to add/update aliases (or use sed)
# Then upload back
scp -P <PORT> /tmp/remote-zshrc <HOST>:~/.zshrc
Required Codex Aliases
alias ccodex='~/symlinks/codex --dangerously-bypass-approvals-and-sandbox --enable steer -c features.skills=true -c model_reasoning_effort=xhigh -c model=gpt-5.2'
alias ccodex-low='~/symlinks/codex --dangerously-bypass-approvals-and-sandbox -c features.skills=true -c model_reasoning_effort=low -c model=gpt-5.2'
alias ccodex-instant='~/symlinks/codex --dangerously-bypass-approvals-and-sandbox -c features.skills=true -c model_reasoning_effort=none -c model=gpt-5.2'
Verify
ssh -p <PORT> <HOST> "source ~/.zshrc && alias | grep ccodex"
9. Git Integration for Remote Repos
Why Include .git?
Without .git, you cannot run git diff on remote to see what's changed. The workflow:
- Local: Commit all changes first
- Transfer: Include
.gitin the zip (but exclude node_modules, target) - Remote: Overlay existing remote files onto unzipped copy
- Diff: Now
git diffshows remote drift vs local committed state
Transfer with .git (Modified Workflow)
# On LOCAL - zip WITH .git but WITHOUT large dirs
cd ~/swe && zip -rq /tmp/project.zip project-name \
-x "project-name/node_modules/*" \
-x "project-name/target/*" \
-x "project-name/.electron-dev/*" \
-x "project-name/__pycache__/*"
# Transfer
scp -P <PORT> /tmp/project.zip <HOST>:/tmp/
# On REMOTE - unzip to /tmp first (don't overwrite existing yet)
ssh -p <PORT> <HOST> "mkdir -p /tmp/swe && cd /tmp/swe && unzip -q /tmp/project.zip"
# Overlay existing remote files onto /tmp copy (preserves .git)
ssh -p <PORT> <HOST> "rsync -a --exclude='.git' ~/swe/project-name/ /tmp/swe/project-name/"
# Check git diff
ssh -p <PORT> <HOST> "cd /tmp/swe/project-name && git diff --stat"
# If satisfied, copy back to final location
ssh -p <PORT> <HOST> "rsync -a /tmp/swe/project-name/ ~/swe/project-name/"
10. Deployment Logs
Create deployment_logs/ directory in each deployed project:
ssh -p <PORT> <HOST> "mkdir -p ~/swe/project-name/deployment_logs"
Log File Format
Create deployment_logs/YYYY_MM_DD.md:
# Deployment Log - YYYY-MM-DD
## Summary
Successfully deployed project-name to remote.
## Agents Involved
| Agent ID | Type | Role |
|----------|------|------|
| abc123... | Claude | Initial deployment |
| def456... | Claude | Git integration |
## Deployment Steps
1. Local commit: `abc1234` - "Commit message"
2. Transfer: Zipped (X MB) and transferred
3. Git integration: Unzipped with .git, overlay, diff
## Key Changes Deployed
- Feature A
- Fix B
- Config C
## Remote Config Differences
- .env: Network bindings differ
- etc.
## Verification
\`\`\`bash
git log --oneline -1
\`\`\`
Commit Deployment Logs
ssh -p <PORT> <HOST> "cd ~/swe/project-name && \
git add deployment_logs/ && \
git commit -m 'Deployment successful - see YYYY_MM_DD.md for details'"
Updated Directory Structure Reference
After complete deployment, the remote machine should have:
~/
├── symlinks/
│ ├── claude -> ~/swe/claude-code-X.Y.Z/cli.js
│ └── codex -> ~/swe/codex.X.Y.Z/codex-rs/target/release/codex
├── swe/
│ ├── claude-code-X.Y.Z/
│ │ ├── .git/ # NEW: git history
│ │ ├── deployment_logs/ # NEW: deployment docs
│ │ ├── cli.js
│ │ └── node_modules/
│ ├── codex.X.Y.Z/
│ │ ├── .git/ # NEW: git history
│ │ ├── deployment_logs/ # NEW: deployment docs
│ │ └── codex-rs/target/release/codex
│ ├── vscode-shims/
│ │ ├── .git/ # NEW: git history
│ │ ├── deployment_logs/ # NEW: deployment docs
│ │ └── ...
│ └── telemetry_projects/ (optional)
├── AgenticProjects/
│ └── agent-box-v1/
│ ├── .git/ # NEW: git history
│ ├── deployment_logs/ # NEW: deployment docs
│ └── ...
├── .claude/
│ ├── .git/ # NEW: git history
│ ├── CLAUDE.md
│ ├── skills/ # NEW: all skills
│ └── .credentials.json
├── .codex/
│ ├── .git/ # NEW: git history
│ ├── AGENTS.md
│ ├── skills/ # NEW: all skills
│ └── auth.json
└── .zshrc # UPDATED: ccodex aliases
Complete Deployment Checklist
Use this checklist to verify a complete deployment:
- Prerequisites: Node.js, Cargo, Python3, Homebrew installed
- Claude Code: Deployed with .git, symlink created
- Codex CLI: Deployed with .git, built, symlink created
- vscode-shims: Deployed with .git, .env configured
- agent-box-v1: Deployed with .git (optional)
- ~/.claude: CLAUDE.md + skills deployed, git initialized
- ~/.codex: AGENTS.md + skills deployed, git initialized
- Auth: .credentials.json and auth.json copied
- Shell aliases: ccodex, ccodex-low, ccodex-instant in ~/.zshrc
- Deployment logs: Created in each project
- Smoke tests: CLI versions verified, aliases working
🚨 Incident: Agent HQ UI Crash (2026-01-25)
Symptom
http://localhost:28037shows "This site can't be reached" / "ERR_CONNECTION_RESET"- Port 8037 not listening on remote
- AMS panel shows "SSE connection lost"
Root Cause
Agent HQ UI (vite) requires environment variables from .env:
AMS_TMUX_PORT(orAGENT_MGMT_PORT)AGENT_HQ_UI_PORT(orVITE_PORT)
The original deployment started Agent HQ without sourcing .env, so the process crashed with:
Error: Missing/invalid AMS port: set AMS_TMUX_PORT (preferred) or AGENT_MGMT_PORT
Error: Missing/invalid port: set AGENT_HQ_UI_PORT (preferred) or VITE_PORT
Fix
Must source .env before starting vite:
ssh -p <PORT> <HOST> "export PATH=\"/opt/homebrew/bin:\$PATH\" && \
tmux new-session -d -s agent-hq-ui-8037 \
-c ~/AgenticProjects/agent-box-v1/apps/agent-hq-ui \
'bash -lc \"cd ~/AgenticProjects/agent-box-v1 && set -a && source .env && set +a && \
cd apps/agent-hq-ui && npm run dev:web -- --port 8037 --host 127.0.0.1; exec bash\"'"
Key pattern: set -a && source .env && set +a exports all variables from .env to the environment.
Prevention
Always start Agent HQ services by:
- Sourcing the repo root
.envfirst - Running in a persistent tmux session
- Using
bash -lcto get a login shell with proper PATH
See skill: remote-agent-hq-startup for complete troubleshooting guide.
OAuth Credential Management
When to refresh and upload credentials:
- Authentication failures on remote machines
- Deploying to a new remote machine for the first time
- After
/loginon local machine (to sync to remotes) - Periodic refresh (every 30-90 days to stay ahead of token expiration)
Quick workflow:
# 1. Generate fresh OAuth (interactive TUI)
mkdir -p /tmp/agent-$(uuidgen | tail -c 6)
CLAUDE_CONFIG_DIR=/tmp/agent-XXXXX CLAUDE_CODE_ENABLE_OAUTH_TOKEN_DUMP=1 ~/swe/claude-code-2.0.28/cli.js
# Type /login in TUI, complete OAuth flow
# 2. Convert to credentials format
python ~/.claude/skills/claude-usage-meter/scripts/convert_oauth_to_credentials.py \
--config-dir /tmp/agent-XXXXX --output ~/.claude/.credentials.json
# 3. Upload to all remotes
python ~/.claude/skills/claude-usage-meter/scripts/upload_credentials_to_remotes.py
Script location: ~/.claude/skills/claude-usage-meter/scripts/upload_credentials_to_remotes.py
How it works:
- Parses SSH aliases from
~/.zshrc(cm3u, cm2, etc.) - Pipes credentials securely via SSH stdin
- Sets correct permissions (600) on remote
- Tests each remote with
~/symlinks/claude -p "test" - Reports success/failure for each remote
Related Skills
- claude-usage-meter - OAuth token refresh and usage API (includes upload_credentials_to_remotes.py script)
- codex-remote-auth - Codex authentication deployment
- config-transparency-centralized - .env configuration philosophy
- search-agent-conversation - Find agents who worked on deployment
- remote-agent-hq-startup - Debug AMS/Agent HQ startup issues