agentic-ecosystem-deployment

star 2

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.

tankygranny05 By tankygranny05 schedule Updated 1/30/2026

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)
  • 0.0.0.0 = NEVER USE - exposes to all networks

⚠️ 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.json to 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 8787 is used by local Claude vscode-shim
  • Port 9288 is used by local Codex vscode-shim
  • Port 8037 is 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:28787remote:8787 (Claude shim)
  • localhost:29288remote:9288 (Codex shim)
  • localhost:28037remote: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/send returns 200 OK
  • Traffic logs show malformed messages ("command": "sendMessage") instead of io_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

  1. Reload page with cache bust: ?v=new
  2. Open console, run: typeof crypto.randomUUID → should return "function"
  3. 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 -9 or kill -9 to 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:

  1. Local: Commit all changes first
  2. Transfer: Include .git in the zip (but exclude node_modules, target)
  3. Remote: Overlay existing remote files onto unzipped copy
  4. Diff: Now git diff shows 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:28037 shows "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 (or AGENT_MGMT_PORT)
  • AGENT_HQ_UI_PORT (or VITE_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:

  1. Sourcing the repo root .env first
  2. Running in a persistent tmux session
  3. Using bash -lc to 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:

  1. Authentication failures on remote machines
  2. Deploying to a new remote machine for the first time
  3. After /login on local machine (to sync to remotes)
  4. 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
Install via CLI
npx skills add https://github.com/tankygranny05/agent-box --skill agentic-ecosystem-deployment
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
tankygranny05
tankygranny05 Explore all skills →