Setup portability — dotfiles repo + bootstrap script
Q23 · Operations & hygiene How do you carry your setup (CLAUDE.md, hooks, skills) across machines?
Max-score answer: “Dotfiles repo including ~/.claude, ~/.codex, ~/.agents, ~/.cursor + bootstrap script.”
Why it matters: Your setup is your productivity. Treating it as throwaway is treating your edge as throwaway.
Why this matters in 2026
Section titled “Why this matters in 2026”The half-life of a developer’s machine in 2026 is shorter than it has ever been. The MacBook you bought in February gets swapped for a new corporate-provisioned one in May; the EU corporate-laptop reset rolled out under DORA-aligned device hygiene rules adds a forced reimage every twelve months; your home Linux box dies; your Codespaces or Coder cloud workspace is rebuilt nightly; you spin up a Vercel Sandbox microVM for a one-off agent run and tear it down four hours later. In each of those moments, the only thing standing between you and a productive first hour on the new machine is whether your setup — CLAUDE.md, hooks, skills, MCP configs, shell aliases, Cursor rules, Codex profile — comes with you or has to be reconstructed from memory.
For the median developer in 2024, “carrying your setup” meant .zshrc and .gitconfig. For the AI-augmented developer in 2026 it means an order of magnitude more state: a 600-line ~/.claude/CLAUDE.md with hard-won project-spanning rules, a ~/.claude/hooks/ directory with five or six bash scripts that gate commits and trigger PR creation, a ~/.claude/skills/ tree with twenty-plus skill folders you’ve authored or forked, a ~/.codex/config.toml with provider profiles and approval policies, a ~/.cursor/rules.json and .cursor-tutor settings, a ~/.agents/ directory of agent-specific MCP configs and tool allowlists, and a ~/.config/claude-code/ with telemetry overrides and feature flags. Reconstructing that by hand on every new machine is not a couple of hours — it is days of slow rediscovery, with the worst kind of “I know I had a rule for this” friction every time you hit a familiar bug your old setup already handled silently.
This is why Q23 sits in the Operations & hygiene section, and why the max-score answer is uncompromising: not “I have a dotfiles repo with my zshrc” but a versioned dotfiles repo that includes every AI tooling directory, paired with a bootstrap script that takes a freshly imaged machine to “ready to work” with one command. Anything less is leaving productivity on the table, and — increasingly — leaving institutional knowledge on the table when you onboard a teammate who would benefit from your CLAUDE.md and skills.
What “max score” actually looks like
Section titled “What “max score” actually looks like”A max-score Q23 setup has one repository on GitHub (private or public, your call), with a deterministic layout, an idempotent bootstrap script, and zero secrets committed. A fresh machine — macOS, Linux, or a Codespace — runs the bootstrap one-liner and ten minutes later you are typing into Claude Code with your full CLAUDE.md, your hooks firing, your skills available, your Codex profiles configured, your Cursor rules applied, and your shell behaving exactly like it does on every other machine you own.
Here is the layout that wins:
~/dotfiles/├── README.md # how to bootstrap, plus a manifest of what's tracked├── bootstrap.sh # idempotent installer — safe to run twice├── home/ # files that mirror into $HOME│ ├── .zshrc│ ├── .gitconfig│ ├── .gitignore_global│ ├── .config/│ │ ├── claude-code/ # feature flags, telemetry│ │ ├── ghostty/│ │ └── starship.toml│ ├── .claude/│ │ ├── CLAUDE.md # your global agent instructions│ │ ├── settings.json # model, hooks, allowlist (no secrets)│ │ ├── hooks/ # *.sh — pre-commit, stop-hooks, etc.│ │ ├── skills/ # your authored skills (one folder each)│ │ └── commands/ # slash commands│ ├── .codex/│ │ ├── config.toml # approval_policy, model_provider, profiles│ │ ├── instructions.md # global Codex instructions│ │ └── prompts/ # named prompts│ ├── .cursor/│ │ ├── rules.json # global Cursor rules│ │ └── mcp.json # MCP servers (op:// references only)│ └── .agents/│ ├── mcp/ # shared MCP server configs│ └── allowlists/ # per-agent tool allowlists├── machine-overrides/ # templated per-host deltas (chezmoi/yadm)│ ├── default.yaml│ ├── work-laptop.yaml│ └── home-linux.yaml└── .gitignore # blocks *.key, *.pem, .env, sessions/The companion bootstrap.sh is dumb on purpose — a flat bash script that any future you can read in 30 seconds:
#!/usr/bin/env bashset -euo pipefail
# 1. Install package managers + core toolsif [[ "$OSTYPE" == "darwin"* ]]; then command -v brew >/dev/null || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew bundle --file=./Brewfileelse sudo apt-get update && sudo apt-get install -y zsh git curl ripgrep fd-find fzffi
# 2. Install agent tooling (idempotent — re-runs are safe)curl -fsSL https://claude.ai/install.sh | bashcurl -fsSL https://openai.com/codex/install.sh | bashcommand -v cursor-agent >/dev/null || npm install -g @cursor/cli
# 3. Link or copy tracked files into $HOME (chezmoi handles this if you use it)chezmoi init --apply --source="$(pwd)/home"
# 4. Authenticate against your secret manager (NEVER store the secret here)op signin || echo "Run 'op signin' once interactively after bootstrap completes."
# 5. Trigger a one-shot sanity checkclaude doctor || truecodex --versioncursor-agent --versionecho "Bootstrap done. Open a new shell."That’s it. No magic, no proprietary install layer, no 800-line provisioner. The principle is “if the script breaks halfway, you can read it and fix the line that broke.” Everything that wants to be smart — templating, encryption, machine-specific deltas — lives inside the dotfile manager (chezmoi or yadm) and not inside the bootstrap.
Current landscape (web-search-verified)
Section titled “Current landscape (web-search-verified)”chezmoi (templated, machine-specific)
Section titled “chezmoi (templated, machine-specific)”chezmoi is the consensus winner in 2026 for anyone with more than one machine. It tracks a source directory in git, renders Go templates (with OS, hostname, and arch as inputs) into your $HOME, and supports native encryption with age or gpg for the few files that need to be in the repo but not in plaintext. Files are tracked by their target name — dot_claude/CLAUDE.md in the source becomes ~/.claude/CLAUDE.md on apply — which keeps the repo browseable on GitHub. A 2026 Zenn writeup Standardizing AI Agent Instructions and Permission Management with chezmoi and Hooks walks through using chezmoi specifically to manage Claude Code and Codex configs with templates that interpolate machine name into allowlists and hook scripts. The one-liner bootstrap (sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply your-github-handle) clones, templates, and applies in a single step — and is rerunnable.
When to pick chezmoi: you have more than one machine, the machines differ in real ways (work vs personal, macOS vs Linux, x86 vs Apple Silicon), and you want some files (like ~/.claude/CLAUDE.md) to be 90% identical with per-machine deltas at the top.
yadm (git-based)
Section titled “yadm (git-based)”yadm is a thin wrapper around git that operates directly on $HOME. Where chezmoi has a source-target split, yadm tracks files in place — yadm add ~/.claude/CLAUDE.md adds the live file to a yadm-managed bare repo at ~/.local/share/yadm/repo.git. It supports per-host overrides through filename suffixes (.zshrc##os.Darwin, .zshrc##hostname.work-mbp), gpg-encrypted files via yadm encrypt, and bootstrap hooks via ~/.config/yadm/bootstrap. It’s lighter than chezmoi conceptually — if you already speak fluent git, yadm feels like a 30-second learning curve — but the templating story is weaker. There’s no Go templates; everything per-machine is an alternate file.
When to pick yadm: you have one or two machines, you already know git extremely well, and your per-machine deltas are small enough to express as alternates.
Plain git symlinking (e.g. stow, hand-rolled)
Section titled “Plain git symlinking (e.g. stow, hand-rolled)”The oldest pattern: a plain git repo, with files in their tracked structure, symlinked into $HOME by stow, dotbot, or a hand-rolled script. It is the simplest thing that works and has the lowest mental overhead — what you see in the repo is what’s on disk. The cost is that machine-specific deltas turn into branches, conditional sourcing in .zshrc, or copy-paste between similar files. For pure AI tooling configs that are 100% identical across machines (your ~/.claude/CLAUDE.md, your ~/.codex/instructions.md, your skills folder), plain symlinking is fine and gives you maximum portability — your repo works with or without any specific dotfile tool installed.
When to pick plain git: you have one machine, or all your machines run identical OS / arch and you don’t need per-host deltas, and you prefer transparent tools over feature-rich ones. natelandau/dotfiles is a good reference repo for this style.
What to include from AI tooling
Section titled “What to include from AI tooling”Track these in your dotfiles, in this order of priority:
~/.claude/CLAUDE.md— your global Claude Code instructions. The highest-value single file in your entire developer setup in 2026. If you have hard-won rules for “always read the file before editing”, “never run destructive git commands without permission”, “auto-PR after substantive work”, they live here.~/.claude/settings.json— model preference, allowlists, hook registration. Strip secrets first; useop://references for anything sensitive.~/.claude/hooks/— every shell script that gates or augments your Claude Code sessions. Theauto-pr-watch.shStop hook, your pre-edit hooks, your file-saved hooks. These are executable code and should absolutely be versioned.~/.claude/skills/— your authored skills, one folder each withSKILL.md+ supporting files. These are your personal expertise encoded for the agent.~/.claude/commands/— your slash commands.~/.codex/config.tomland~/.codex/instructions.md— Codex provider profiles, approval policies, and your global instructions.~/.codex/prompts/— named prompt templates.~/.cursor/rules.json— Cursor rules (global rules for inline edits and agent mode).~/.cursor/mcp.json— Cursor’s MCP server registry, withop://references for any tokens.~/.agents/— shared MCP server configs, tool allowlists, and other cross-agent state.~/.config/claude-code/— telemetry overrides, feature flags, beta opt-ins..mcp.jsontemplates — keep a starter.mcp.jsonindotfiles/templates/so new projects bootstrap MCP servers from a known-good baseline.
What NOT to include
Section titled “What NOT to include”Just as important. These are the categories that break the moment they hit a public or even private repo:
- Secrets in any form. No
OPENAI_API_KEY=sk-..., no GitHub tokens insettings.json, no Anthropic admin keys in MCP configs. If a file references a secret, it should referenceop://Personal/openai/credential(or your secret manager’s equivalent), and the secret manager handles resolution at runtime. See Q22 for the full pattern. - Cookies and session tokens. No
.config/gh/hosts.ymlif it contains a session token. No~/.claude/sessions/. No.cursor-tutorif it stores auth. - Machine-specific state. Cache directories (
~/.claude/cache/), logs (~/.claude/logs/), telemetry IDs (~/.config/claude-code/machine-id), Codex run history. None of this should follow you across machines — it’s noise at best, fingerprinting at worst. - Personal identity that varies per machine. SSH host keys (per-machine), system-wide hostnames, mDNS aliases.
- Anything you wouldn’t want a teammate to find if you share the repo URL with them tomorrow. Run
gitleaks detectandtrufflehog filesystem .on the repo before pushing.
A .gitignore that captures the common offenders for the AI tooling tree:
# AI tooling — never track these**/sessions/**/cache/**/logs/**/.history/**/auth.json**/credentials.json**/*.key**/*.pem.env.env.localStep-by-step: setting up a portable dotfiles repo
Section titled “Step-by-step: setting up a portable dotfiles repo”-
Create the repo, decide on the manager. Make a new GitHub repo called
dotfiles(private if it includes anything you’d rather not display, public if you want it as a portfolio piece — most people pick private). Decide betweenchezmoi(templating),yadm(git-in-$HOME), or plainstow-style symlinking. If unsure, pick chezmoi. -
Audit your current
$HOMEfor AI tooling. Runls -la ~/ | grep -E '(claude|codex|cursor|agent)'andfind ~/.config -maxdepth 2 -name '*claude*' -o -name '*codex*' -o -name '*cursor*'so you know what’s actually on disk. Don’t skip this — most people are surprised by what they find. -
Pull the tracked files into the source directory. With chezmoi:
chezmoi add ~/.claude/CLAUDE.md, thenchezmoi add ~/.claude/settings.json, thenchezmoi add ~/.claude/hooks/, and so on. With yadm:yadm add ~/.claude/CLAUDE.mdand the rest. With plain git: move the files into the repo and replace them with symlinks. -
Strip secrets and replace with references. Open each tracked file and find anything sensitive — API keys, OAuth tokens, account IDs, internal hostnames. Replace each with a secret-manager reference (
op://Personal/...for 1Password,${DOPPLER_TOKEN}for Doppler, etc.). Re-test that the tool still works through the manager wrapper (op run -- claude). -
Write
bootstrap.sh. Start with the skeleton in What “max score” actually looks like and adapt to your stack — Homebrew on macOS, apt on Linux, your specific Codex install method, the npm globals you want. Test it twice: once on a fresh VM (UTM, OrbStack, or a GitHub Codespace), once on your existing machine to confirm it’s idempotent and doesn’t clobber state. -
Add a
.gitignoreand pre-commit secret scan. Copy the.gitignorefrom above. Installgitleaksand add a pre-commit hook (gitleaks protect --staged). Rungitleaks detect --source .once over the whole tree before the first push. -
Push and bootstrap a second machine. Push to GitHub, then on a different machine (or a fresh VM) run the chezmoi one-liner (
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply YOUR-HANDLE) or your equivalent. Time the run. If it’s over 10 minutes for “ready to work”, trim the bootstrap. -
Document the README. Two paragraphs: how to bootstrap, and what’s tracked. The second is the manifest — when future-you wonders “where did I put my Claude hooks?”, the README answers in five seconds.
-
Set up a refresh routine. Once a month,
chezmoi re-add(oryadm addagain) over your tracked files to pull in drift. Once a quarter, run the full bootstrap on a throwaway VM to confirm it still works end-to-end. -
Onboard one teammate. Hand them the repo URL. If they’re at “ready to work” inside an hour, you’ve succeeded. If they hit five undocumented prerequisites, fix the bootstrap until those five are automated.
Common pitfalls
Section titled “Common pitfalls”Committing secrets. The single most frequent mistake — an ~/.claude/settings.json with an inline API key, a .codex/config.toml with a provider key, a mcp.json with a bearer token. Always pipe everything through your secret manager (Q22), and add gitleaks to the pre-commit. If you push a secret by accident, rotate it immediately — assume the repo is public from the moment of the push, even if it’s private, because GitHub’s secret-scanning APIs already saw it.
No bootstrap script. A repo with files but no installer. You end up with a 14-step README that requires careful reading every time you onboard a new machine. The point of dotfiles is automation — if you have to think every time, you’ll skip it on the next provisioning event. Even a 20-line bash script is infinitely better than a markdown checklist.
Machine-specific absolute paths. Hardcoding /Users/yourname/... into CLAUDE.md or settings.json is a portability bug waiting to bite the moment your username on the new machine is different (Codespaces uses vscode; corporate macOS often uses firstname.lastname). Use $HOME, ~, or chezmoi’s {{ .chezmoi.homeDir }} template.
Tracking caches and logs. ~/.claude/cache/, ~/.claude/logs/, ~/.config/claude-code/machine-id — they bloat the repo, leak machine fingerprints, and create merge conflicts. Gitignore them aggressively.
Forgetting to handle drift. You edit ~/.claude/CLAUDE.md directly, never run chezmoi re-add, and a month later your repo is stale. Either commit to editing the source (chezmoi edit ~/.claude/CLAUDE.md) or set a monthly reminder to re-add tracked files.
No second machine. A dotfiles repo that’s never been bootstrapped on a second machine is theatre — you don’t actually know if it works. Spin up a Codespace once a quarter and run the bootstrap end-to-end as a fire drill.
Conflating dotfiles with project rules. Project-specific instructions live in the project’s own CLAUDE.md (committed to the project repo). Global, cross-project rules live in ~/.claude/CLAUDE.md (your dotfiles). Don’t mix them — the global one should be useful regardless of which project you’re in.
How to verify you’re there
Section titled “How to verify you’re there”Run through this checklist. Every box checked = max score. Skipped boxes = action items.
- A GitHub repo named
dotfiles(private or public) that I own and have pushed in the last 30 days. - The repo contains
~/.claude/CLAUDE.md,settings.json,hooks/,skills/, andcommands/(or chezmoi/yadm equivalents). - The repo contains
~/.codex/config.toml,instructions.md, andprompts/. - The repo contains
~/.cursor/rules.jsonandmcp.json. - The repo contains
~/.agents/with shared MCP configs and allowlists. - A
bootstrap.shexists, is executable, and is idempotent (safe to run twice). - Running the bootstrap on a fresh VM gets me to “open Claude Code with my full setup” in under 10 minutes.
-
gitleaks detect --source .returns clean over the full repo. - No plaintext secrets anywhere in the tree — only
op://(or equivalent) references. -
.gitignoreblockssessions/,cache/,logs/,.env,*.key,*.pem. - The README has a one-liner bootstrap command and a manifest of what’s tracked.
- I’ve bootstrapped at least one machine other than my primary in the last 90 days.
- A monthly calendar reminder exists for “re-add drift” or I edit through
chezmoi editexclusively.
If all twelve are ticked, you’re at max score on Q23. If you’re at eight to eleven, you’re at a 3 or 4 and one focused afternoon away from max. Below eight, treat this as your highest-leverage Operations & hygiene investment for the quarter — the compounding return on “every new machine is ready in 10 minutes” is enormous.