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
<!-- [Added by Claude: 3ae26ab6-64d2-4932-8ed8-4ca6f4313658 2026-01-25] -->⚠️ 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
<!-- [Added by Claude: 3ae26ab6-64d2-4932-8ed8-4ca6f4313658 2026-01-25] -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: This breaks remote webview completely - agents must know this immediately -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: Electron apps spawn zombie processes that are hard to kill remotely -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: Skills and CLAUDE.md/AGENTS.md are essential for agent functionality -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: Aliases like ccodex provide consistent CLI experience across machines -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: .git enables git diff on remote to track drift from local committed state -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] --> <!-- Rationale: Document what was deployed for future reference and audit trail -->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
<!-- [Added by Claude: 1446bf2e-3669-4050-a5ba-48be09357f30] -->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)
<!-- [Added by Claude: 3ae26ab6-64d2-4932-8ed8-4ca6f4313658 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