Skip to content

AI Coding-Agent Dev Environment — Setup Runbook

Date: 2026-05-22 Machine: macOS 26.3.1 (build 25D2128), Apple Silicon (arm64) Decision: ADR-022 — AI coding-agent orchestration & terminal dev environment

Terminal-native stack for running Claude Code (planner/reviewer) + Codex (primary implementer) together: Ghostty → tmux → Agent of Empires (AoE) → lazygit + git-delta. This records what was actually installed and configured on the date above, with the real versions, so it can be rebuilt on a fresh machine.

Two install paths since ADR-023:

  • Per-client (Mac) — sections 1–6 below + Redeploy a Mac. Ghostty, theming, the local terminal feel.
  • Persistent host (homelab agents LXC)section 7. The actual always-on runtime; makes the "from anywhere" access tiers work. This is the recommended target.

Installed versions (as built, 2026-05-22)

Tool Version Source
Ghostty 1.3.1 brew install --cask ghostty
tmux 3.6b already present (skipped)
Agent of Empires (aoe) 1.8.0 brew install aoe
lazygit 0.61.1 brew install lazygit
git-delta (delta) 0.19.2 brew install git-delta
difftastic (difft) 0.69.0 brew install difftastic
Docker Desktop 4.74.0 brew install --cask dockerpending (see note)
Claude Code (claude) 2.1.148 npm @anthropic-ai/claude-code
Codex (codex) 0.133.0 npm @openai/codex
git 2.54.0

Docker note: brew install --cask docker needs sudo (to create /usr/local/cli-plugins) and cannot run unattended. Run it yourself in a real terminal; then launch Docker Desktop once before using sandboxed agents.


1. Install the stack (Homebrew)

# Terminal + review tools + orchestrator (tmux was already installed → skipped)
brew install lazygit git-delta difftastic aoe
brew install --cask ghostty

# Docker Desktop — run in a real terminal; it will prompt for your password
brew install --cask docker

2. Agent CLIs (already installed & logged in)

# Claude Code
npm install -g @anthropic-ai/claude-code
claude          # then run /login inside the TUI

# Codex
npm install -g @openai/codex
codex login

Both were already present and authenticated on the build machine (~/.claude.json has an oauthAccount; ~/.codex/auth.json exists).

3. Configure git-delta (pretty diffs everywhere)

The config lives version-controlled in this repo at dev-env/delta.gitconfig; the live copy is included from a stable path so it survives deleting ~/Downloads.

# from the homelab-docs repo root:
mkdir -p ~/.config/git
cp docs/workstation/dev-env/delta.gitconfig ~/.config/git/delta.gitconfig
printf '\n[include]\n\tpath = ~/.config/git/delta.gitconfig\n' >> ~/.gitconfig

# verify:
git config --get core.pager      # -> delta

On-disk location: ~/.config/git/delta.gitconfig, referenced by an [include] line in ~/.gitconfig.

4. Configure lazygit

Version-controlled at dev-env/lazygit-config.yml.

# from the homelab-docs repo root:
mkdir -p "$(lazygit --print-config-dir)"
cp docs/workstation/dev-env/lazygit-config.yml "$(lazygit --print-config-dir)/config.yml"

On-disk location (this machine): ~/Library/Application Support/lazygit/config.yml (lazygit --print-config-dir is authoritative — on Linux it's ~/.config/lazygit).

5. First run with AoE (on the ready repo)

Prereqs: tmux installed (✓), and Docker Desktop running if you use --sandbox.

cd /Users/gabriel/git/ready

aoe                              # launch the TUI

aoe add --cmd claude            # Claude Code session (planner/reviewer)
aoe add --sandbox --cmd codex   # Codex session, sandboxed in Docker (primary coder)

aoe serve                       # optional: web dashboard for phone access

TUI keys: n new session · Enter attach · t toggle terminal · D diff view · d delete · ? help. Ctrl+b d detaches an agent and returns to AoE. Per-repo settings/hooks live in .aoe/config.toml.

6. Review loop

While agents work, use lazygit (run lazygit in the repo, or D in AoE) to read each agent's diff with delta highlighting, then merge the worktree branch when satisfied — the Claude-plans / Codex-codes / you-and-Claude-review loop, fully in the terminal.


7. Homelab persistent install (ADR-023)

On 2026-05-28 the agent stack moved off the Mac into a dedicated Proxmox LXC (agents, LXC 135) per ADR-023. The Mac sections above remain for per-client polish (Ghostty, theming); the homelab install is what makes the "from anywhere" access tiers actually work.

Source of truth: chizuru-v2/ansible/playbooks/agents.yml. The post-install interactive steps live in that playbook's header comment so they travel with the IaC.

Deploy / re-deploy

# From the chizuru-v2 repo on your workstation:
ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/agents.yml

Idempotent — re-run any time. It (re-)installs nested Docker, Node 20, pinned npm globals (@anthropic-ai/claude-code, @openai/codex), version-pinned AoE / lazygit / difftastic release tarballs, the claude non-root service user, a git credential helper (no Vault tokens embedded in repo URLs), repo clones (holo/homelab-docs, senior-discount/ready), and the two systemd units in services/agents/.

The claude-remote-control.service unit ships disabled — auth must be seated interactively first; enabling pre-auth would crash-loop the daemon on reboot.

Post-install (one-time, interactive)

These seven steps seat everything the systemd daemon needs before it can serve a usable Remote Control session. Each step's symptom-when-skipped is in lessons-learned 2026-06-16; the short version is all of them are required, and the daemon's failure mode for missing steps 4–6 is "browser session works but /mcp is empty" — i.e. silent.

# Reach the host. From outside the LAN, use the netbird hostname:
ssh chizuru                                          # or: ssh [email protected] on LAN
su - claude
tmux new -s login                                    # so we survive ssh hiccups

# ──────────────────────────────────────────────────────────────────────
# 1. Seat Claude CLI credentials (writes ~/.claude/.credentials.json).
#    Use the auth subcommand, NOT the TUI's /login — the TUI's wizard
#    triggers a workspace-trust + remote-control re-flow we'd rather
#    handle declaratively below.
# ──────────────────────────────────────────────────────────────────────
claude auth login --claudeai
# follow the printed URL on your Mac, paste the returned code back
claude auth status                                   # expect loggedIn: true
claude --print "ping"                                # expect "OK"-ish reply
                                                     # (do NOT pass --bare —
                                                     #  --bare disables OAuth reads
                                                     #  and produces a false negative)

# ──────────────────────────────────────────────────────────────────────
# 2. Seat Codex credentials (writes ~/.codex/auth.json).
#    --device-auth is hidden from --help but is the headless path; codex
#    will print the URL+code, accept it on your Mac.
# ──────────────────────────────────────────────────────────────────────
codex login --device-auth

# ──────────────────────────────────────────────────────────────────────
# 3. Confirm the ready repo is cloned at the path the daemon expects.
#    (The playbook clones it for you; this is a sanity check.)
# ──────────────────────────────────────────────────────────────────────
test -d /home/claude/git/ready/.git && echo OK || echo MISSING

# ──────────────────────────────────────────────────────────────────────
# 4. Seat workspace trust for /home/claude/git/ready.
#    Skips the "Do you trust this workspace?" prompt that would
#    otherwise block the daemon on first start.
# ──────────────────────────────────────────────────────────────────────
jq '.projects["/home/claude/git/ready"].hasTrustDialogAccepted = true' \
  ~/.claude.json > ~/.claude.json.new && mv ~/.claude.json.new ~/.claude.json

# ──────────────────────────────────────────────────────────────────────
# 5. Allow-list the project's .mcp.json servers (this is required —
#    .mcp.json alone does NOT auto-enable). Otherwise /mcp shows the
#    codex tools as missing even though .mcp.json declares them.
# ──────────────────────────────────────────────────────────────────────
jq '.projects["/home/claude/git/ready"].enabledMcpjsonServers = ["codex"]' \
  ~/.claude.json > ~/.claude.json.new && mv ~/.claude.json.new ~/.claude.json

# ──────────────────────────────────────────────────────────────────────
# 6. Accept the per-machine "Enable Remote Control?" dialog once.
#    Without this, the daemon exits with the misleading error
#    "Remote Control is not yet enabled for your account".
# ──────────────────────────────────────────────────────────────────────
echo y | claude remote-control --name ready --spawn=worktree &
sleep 5
pkill -x claude                                      # foreground only; daemon starts via systemd
# Confirm remoteDialogSeen now sticks:
jq -r '.remoteDialogSeen' ~/.claude.json             # expect: true

# ──────────────────────────────────────────────────────────────────────
# 7. Bring the systemd unit up and verify.
# ──────────────────────────────────────────────────────────────────────
exit                                                 # back to root
systemctl enable --now claude-remote-control
systemctl status claude-remote-control               # expect active (running)
journalctl -u claude-remote-control -n 30 --no-pager # expect "Remote Control session started"

After step 7, the ready environment will show up on claude.ai/code under its name (not as an env_… ID — the ID rotates each daemon restart, the name is the stable handle). Open it, ask Claude something, then run /mcp to confirm mcp__codex__codex and mcp__codex__codex-reply are listed — that's the canary for the ReadWritePaths fix from lessons-learned 2026-06-16.

Access tiers (per ADR-023)

Device Network Access path Personal creds on device?
Personal Mac at home Home LAN or Netbird SSH → tmux → AoE on agents yes
iPhone / iPad Cellular or wifi Blink Shell + Netbird + mosh → tmux → AoE; or claude.ai/code via Safari / Claude mobile app yes
Work-issued laptop Corp VPN Browser only at claude.ai/code. No Netbird, no SSH keys, no install. NO
Borrowed device Any Browser to claude.ai/code NO

When to re-deploy

  • Tool version bump — edit claude_code_version / codex_version / aoe_version / lazygit_version / difftastic_version vars in agents.yml, push, re-run.
  • Service hardening or environment change — edit services/agents/claude-remote-control.service.j2, re-run.
  • New repo to clone — add to forgejo_repos in agents.yml, re-run.

Operations

  • Logs: journalctl -u claude-remote-control -f on the LXC, or via Alloy → Loki → Grafana with alloy_job_name: agents.
  • Failure alerts: OnFailure=ntfy-alert@%n.service POSTs to https://ntfy.eva-00.network/homelab-alerts after StartLimitBurst=5 failures within StartLimitIntervalSec=15min.
  • Restart: systemctl restart claude-remote-control — issues a fresh environment= URL on claude.ai/code; old in-flight browser sessions detach. The Glance link is intentionally the env-list page (https://claude.ai/code), not a per-session URL.

Caveats pinned by ADR-023

  • Never set ANTHROPIC_API_KEY, DISABLE_TELEMETRY, or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC on the unit's Environment= — any of them breaks Remote Control eligibility silently.
  • Do not copy ~/.claude.json from the Mac. Linux Claude Code stores credentials at ~/.claude/.credentials.json; the macOS file is not the credential.
  • Treat ~/.codex/auth.json as a password-equivalent, never as routine IaC state. Rebuild path is agents.yml + interactive device-code login, not credential restore.

Redeploy the Mac dev env on a fresh machine

# 0. Homebrew (if missing): https://brew.sh
# 1. Stack
brew install lazygit git-delta difftastic aoe
brew install --cask ghostty
brew install --cask docker          # run in a real terminal (needs sudo); launch it once

# 2. Agent CLIs + login
npm install -g @anthropic-ai/claude-code @openai/codex
claude        # /login
codex login

# 3. Configs from this repo (clone homelab-docs first, then from its root)
mkdir -p ~/.config/git
cp docs/workstation/dev-env/delta.gitconfig ~/.config/git/delta.gitconfig
printf '\n[include]\n\tpath = ~/.config/git/delta.gitconfig\n' >> ~/.gitconfig
git config --get core.pager        # -> delta
mkdir -p "$(lazygit --print-config-dir)"
cp docs/workstation/dev-env/lazygit-config.yml "$(lazygit --print-config-dir)/config.yml"

# 4. Work
cd /path/to/ready
aoe
aoe add --cmd claude
aoe add --sandbox --cmd codex