I’ve started to play with Claude Code, but, being paranoid, I’ve not just run it in a terminal. Here are some brief notes about what I did do.

The basic idea is to run Claude in a VM. There’s nothing new here: it’s a path many have trodden.

It took me a little while to find an approach that felt both safe and reasonably ergonomic. The general idea is to have three directory trees:

I’m using Git worktrees1 to have two branches of the same repo in the file system at once.

Copying files in and out of the VM is done with rsync over SSH, and I’ve found these two scripts to be useful:

push

#! /bin/sh

. git-guards.sh

require_git_branch claude || exit 1

rsync \
      --verbose \
      --recursive \
      --checksum  \
      --itemize-changes \
      --exclude='/.git' \
      --exclude='/tmp'  \
      --exclude='/push' \
      --exclude='/pull' \
      --delete \
      ./ toy-vf.local:claude/foo/

pull

#! /bin/sh

. git-guards.sh

require_git_branch claude || exit 1

rsync \
      --verbose \
      --recursive \
      --checksum  \
      --itemize-changes \
      --exclude='/.git' \
      --exclude='/tmp'  \
      --exclude='/push' \
      --exclude='/pull' \
      --delete \
      mjo@toy-vf.local:claude/foo/ ./

git-guards.sh

# Shared git guard helpers.
# Source this file; do not execute it directly.

require_git_branch() {
  required_branch="$1"
  repo_dir="${2:-.}"

  if [ -z "$required_branch" ]; then
    echo "Usage: require_git_branch <branch> [repo_dir]" >&2
    return 2
  fi

  if ! command -v git >/dev/null 2>&1; then
    echo "Error: git is not installed or not on PATH" >&2
    return 127
  fi

  current_branch="$(git -C "$repo_dir" branch --show-current 2>/dev/null)" || {
    echo "Error: '$repo_dir' is not a git repository" >&2
    return 1
  }

  if [ -z "$current_branch" ]; then
    echo "Error: '$repo_dir' is in detached HEAD state; expected branch '$required_branch'" >&2
    return 1
  fi

  if [ "$current_branch" != "$required_branch" ]; then
    echo "Error: expected branch '$required_branch', currently on '$current_branch'" >&2
    return 1
  fi
}

A few comments:

Typical workflow

  1. Use Claude Code to make changes to the code in the VM.

  2. Use the pull script to copy the code into the claude branch of the repo. Carefully check the code.

  3. Use git or filesystem commands to copy the changes into the main branch of the repo.