When something's not right
Troubleshooting: the calm fixes for the usual snags.
Most "it's broken" moments after a fresh install are one of a dozen known things — a permission macOS won't let a script grant, a step that needs a restart, a tool that just needs re-applying. Here's the map, roughly in the order you'll hit them.
Before anything else: preview and re-apply the setup. chezmoi diff shows what's out of sync, chezmoi apply puts managed files back, and chezmoi update pulls the latest first. The repo is the source of truth for managed files, so start there before hand-editing generated config.
The first apply creates a focused backup under ~/.local/state/groundwork/backups/latest. Run groundwork-restore to restore the backed-up managed files and macOS defaults. It does not uninstall apps or undo Homebrew upgrades.
#Quick triage
| A command isn't found | The shell hasn't picked up a newly-installed tool. Run exec zsh (reloads the shell) or open a new tab |
| A config edit did nothing | You edited the file in your home folder, not the repo. Edit with chezmoi edit, or your change gets overwritten on next apply — see Setup |
| Icons show as boxes □ | The terminal font isn't the Nerd Font. Groundwork installs JetBrainsMono Nerd Font; re-run chezmoi apply, restart Ghostty, and check with ghostty +show-config | grep -i font-family |
| Ghostty opens in an old project | A fresh app launch should open in ~. This setup disables Ghostty window-state restore and lets tmux own project restoration. Re-run chezmoi apply, quit Ghostty, and launch it again |
| A tool looks unconfigured | It needs its one-time setup step below (tmux plugins, Neovim sync, Karabiner permission…) |
| Git keeps asking to log in | An auth issue, not a setup bug — jump to Git access |
#Install & bootstrap
Homebrew says the Xcode license is not accepted
Fix the first error, then re-run bootstrap-mac.sh. If Homebrew stops at the Xcode license check, the later gh: command not found or chezmoi: command not found messages are just follow-on errors because those tools never installed.
$ sudo xcodebuild -license accept
$ bash -c "$(curl -fsSL https://raw.githubusercontent.com/jasondockery/groundwork/main/bootstrap-mac.sh)"
Homebrew also requires the current macOS user to be an Administrator. If sudo -v fails, add that user as an admin first or run the install from an admin account.
You cannot find Xcode after setup
Groundwork always handles Apple's Command Line Tools, not full Xcode by default. If xcode-select -p prints /Library/Developer/CommandLineTools, the terminal build layer is present. Full Xcode installs only when you answer yes to the Xcode setup prompt, and even then the App Store install can fail if you are not signed in or Xcode has never been obtained on that Apple Account. If you need it, get Xcode from the Mac App Store, open it once, then run sudo xcode-select -s /Applications/Xcode.app/Contents/Developer.
Bootstrap URL returns 404
A 404 from raw.githubusercontent.com usually means the branch name, path, or URL is wrong, or GitHub is temporarily unreachable. The public bootstrap URL should work without GitHub login. Copy it from the README again, or open the URL in a browser to see the actual GitHub error.
Homebrew skipped or failed a package
On a brand-new or beta macOS, Homebrew sometimes has no pre-built bottle for a formula and either builds it slowly from source or skips it. brew bundle deliberately continues past failures, so the install finishes — you just re-run the stragglers:
$ brew bundle --file=~/.config/homebrew/Brewfile # retry everything (idempotent)
$ brew install <formula> # or just the one that failed
$ brew doctor # diagnose a broken install
The install scripts are written to be re-run safely: anything that failed (a network blip, a tool that wasn't ready) is picked up on the next chezmoi apply, and steps that already succeeded are skipped. So a clean re-apply is the right first response to a half-finished install.
Downloads fail with curl: (92) or the fetch stalls
Repeated HTTP/2 stream was not closed cleanly: PROTOCOL_ERROR failures across several vendors usually point to the connection — a VPN, proxy, travel router, or guest Wi-Fi — though a vendor outage or a local ~/.curlrc can look the same; it is rarely a broken setup. When downloads fail, the update re-fetches just the failed ones, one at a time with backoff, and leaves everything that succeeded alone. If a large parallel fetch stalls after a download error, pressing CtrlC and re-running update-all is safe in the sense that installed packages stay installed and completed downloads are normally cached — but the rerun repeats the normal update phases rather than resuming mid-fetch, so skim the final output before retrying.
Homebrew warns: "The following taps are not trusted"
Homebrew now requires you to explicitly trust third-party taps before it will use their formulae and casks — a supply-chain protection, and the taps it lists are ones you (or a tool you ran) added; Groundwork adds none. Ignore the warning and those taps stay inert. To keep using something from one, prefer trusting just the formulae you actually use over whole-tap trust, which also covers everything the tap publishes in the future:
$ brew trust --formula hashicorp/tap/terraform # narrow: one formula
$ brew untap someuser/sometap # or drop a tap you no longer use
A Python tool tries to build SciPy or asks for gfortran
Global Python tools intentionally use a proven Python version, not the newest Python release. Heavy dependencies such as NumPy and SciPy need mature wheel coverage; when a tool runs on a too-new Python, uv can fall back to a slow source build and fail on a normal Mac because no Fortran compiler is installed. Keep the global tool Python on the Groundwork-managed version, and choose newer Python versions per project with mise use python@... only when that project needs them.
Aider crashes with audioop or pyaudioop
That means Aider was installed into a too-new Python tool environment. Groundwork keeps Aider's isolated uv runtime on Python 3.12 for now, because Python 3.13 removed audioop and Aider's voice dependency path can still import it on startup. The global mise Python remains 3.13; mise also provisions 3.12 so this tool pin is declared. Repair it with:
$ uv tool install --python 3.12 --reinstall aider-chat
$ aider --version
"command not found" right after install
Your current shell started before the tool existed. Reload it — exec zsh — or open a new terminal tab. Still missing? Confirm Homebrew actually installed it with brew list <name>, and that Homebrew is on your path with which brew (should be /opt/homebrew/bin/brew).
Restore the pre-Groundwork snapshot
Use this when the setup applied successfully but the person decides they do not like the opinionated defaults. The helper runs the latest first-run backup's restore script and asks for confirmation.
$ groundwork-restore
# or inspect the backup first:
$ open ~/.local/state/groundwork/backups/latest
The restore is intentionally limited to the files and macOS defaults this repo snapshots. It does not uninstall software, remove Homebrew packages, undo app permissions, or replace a full system backup.
Old configs are competing with the setup
On a Mac that has been configured by hand for years, old editor, tmux, shell completion, or plugin state can compete with this setup. During chezmoi init, answer yes to the reset prompt for a clean start. The reset moves known conflict zones to ~/.local/state/groundwork/reset/latest before applying Groundwork.
| Moved aside | Neovim config/state/cache, tmux legacy config/plugin/restore state, and .zcompdump* completion caches |
| Not moved | Raycast's internal database, app permissions, browser profiles, arbitrary project folders, and unrelated dotfiles |
| Run later | Edit ~/.config/chezmoi/chezmoi.toml, set reset_existing_configs = true, remove ~/.local/state/groundwork/reset/.reset-existing-configs-done if present, then run chezmoi apply |
chezmoi warns: "config file template has changed, run chezmoi init"
This appears after a bare chezmoi update when Groundwork's setup template changed. Running chezmoi init again is not starting over: every answer you already gave is kept (the questions are ask-once), so it regenerates the config from your existing answers. If no new question was added, it returns silently — that is success, not a no-op: the config was rebuilt with your saved answers and nothing was reinstalled. update-all and chezmoi update --init handle this automatically, which is why they are the recommended forms:
$ chezmoi init # regenerate from existing answers (silent = success)
$ chezmoi apply # apply with the refreshed config
To change a setting (not just regenerate), use groundwork-configure: it shows your current answers, changes only the one you pick, previews the diff, and applies after you confirm. Plain chezmoi init never asks what to change, and chezmoi init --prompt replays the entire interview using template defaults — which can silently reset answers — so it is an advanced/debugging path, not the way to tweak one setting.
Windows & WSL: scripts fail with bad interpreter: ^M
That ^M is a Windows carriage return: the clone was made with Git for Windows converting files to CRLF line endings, which breaks shell scripts and chezmoi templates the moment WSL runs them. Groundwork ships a .gitattributes that forces LF, so a fresh clone made inside WSL checks out correctly. If you hit this, re-clone from a WSL terminal, and tell Git to never introduce CRLF again:
$ git config --global core.autocrlf input
$ git clone https://github.com/jasondockery/groundwork.git
A clone made before the fix stays broken even after git pull, because Git only rewrites the files a pull touches; everything else keeps its CRLF endings, including the chezmoi .tmpl files. Instead of re-cloning you can renormalize in place. This discards uncommitted local changes, so commit or stash first:
$ git rm --cached -r .
$ git reset --hard
#tmux
| Plugins missing on first run | Groundwork runs TPM's installer during chezmoi apply. If a network hiccup skipped it, re-run chezmoi apply, or press Ctrla then ShiftI inside tmux to repair manually |
| TPM update pane looks frozen | Ctrla then ShiftU is TPM's plugin update binding, not a built-in tmux command. If its pane does not repaint, wait a few seconds, then press Esc or Enter to reveal or dismiss the summary. To watch normal output instead, run ~/.config/tmux/plugins/tpm/bin/update_plugins all from a shell. |
| Config edits not applying | Reload with Ctrla then r, or fully restart: tmux kill-server then reopen. Because continuum restore is on, panes may come back from the saved snapshot |
| Panes look frozen during apply | Running chezmoi apply inside tmux is okay. First check whether a long install is still running, especially uv, mise, Homebrew, or Aider. If nothing is running and output is frozen, press CtrlQ; use CtrlC only for a truly stuck command. For the fuller tmux-specific flow, see tmux. |
| Status bar looks plain / broken | The Catppuccin theme didn't load, tmux/plugin files changed under a live server, or TPM plugins are missing. Literal placeholders like #{cpu_percentage} mean tmux-cpu or tmux-battery has not loaded. Re-run chezmoi apply, reload with Ctrla then r, or restart tmux from a fresh shell. See tmux for the plugin details. |
| sesh still lists projects after kill-server | The Ctrla then T picker shows live sessions plus zoxide directory candidates. Those folder rows are places to start a session, not proof that tmux sessions survived. Run tmux ls to see only live sessions. |
| Sessions didn't come back | Auto-restore (continuum) only fires on a fresh tmux server start, only restores the latest saved snapshot, and is intentionally off in the CI and agent container profile. tmux kill-server does not save first. Check ~/.local/share/tmux/resurrect/, or restore by hand with Ctrla then Ctrlr. |
| Sessions came back after kill-server | That is continuum restoring the last saved snapshot, not live sessions surviving the kill. For a clean start, detach or open a fresh Ghostty tab and run tmux-fresh. |
See the tmux page for what each plugin does and how saving/restoring works.
#Neovim
The first time you open nvim, LazyVim downloads and compiles its plugins — let it finish before judging anything. After that, two commands diagnose almost everything:
:Lazy # plugin manager — press U to update, X to clean
:checkhealth # reports missing tools (node, rg, fd…) and how to fix
If :checkhealth flags a missing command-line tool, install it with brew install <tool> — Neovim's extras lean on the same CLI tools from the Shell page (ripgrep, fd). If the editor ever gets truly wedged, it's safe to wipe its state and let it rebuild: rm -rf ~/.local/share/nvim ~/.local/state/nvim, then reopen.
#Shell & prompt
| No grey autosuggestions / no colors | The plugins didn't load. Run exec zsh; if it persists, check ~/.zsh_plugins.txt exists and that antidote is installed (brew list antidote) |
| Prompt shows boxes or wrong glyphs | Terminal font isn't a Nerd Font — Groundwork sets Ghostty to JetBrainsMono Nerd Font. Re-run chezmoi apply, restart Ghostty, then verify with ghostty +show-config | grep -i font-family |
| Tab completion feels broken | The completion cache went stale. Rebuild it: rm -f ~/.zcompdump* then exec zsh |
| Slow shell startup | Time it with hyperfine 'zsh -i -c exit'; a recently-added plugin is the usual cause |
#Claude Code
| Esc doesn't cancel in the chat | Deliberate. Terminal and vim fingers hit Esc reflexively, so Groundwork ships ~/.claude/keybindings.json with the Chat-context Esc cancel binding removed. Stop running work deliberately with CtrlC (the hardcoded interrupt — can't be rebound or lost); undo a wrong turn with /rewind. Want stock behavior back? Remove the "escape": null line with chezmoi edit --apply ~/.claude/keybindings.json |
| Keybinding edits keep reverting | The file is chezmoi-managed, and Claude Code's /keybindings shortcut opens the applied copy — edits there are overwritten on the next apply. Make changes with chezmoi edit --apply ~/.claude/keybindings.json instead |
| Keybindings file seems ignored | Custom keybindings need Claude Code v2.1.18 or later — check with claude --version. On a current version, run /doctor inside Claude Code — it has a section that validates ~/.claude/keybindings.json and names the exact problem |
#Docker
| Desktop says "unable to find docker zsh completion in your fpath" | Groundwork generates completions into ~/.docker/completions on every apply, and .zshrc adds that folder to fpath when it exists. In a new terminal, verify: ls ~/.docker/completions/_docker, then type docker con and press the Tab key (don't paste "<Tab>" — zsh reads it as a redirect and throws a parse error). If the file is missing, check command -v docker and re-run chezmoi apply. If completion works, the banner is cosmetic — Desktop greps ~/.zshrc for its own snippet. Don't click its Install button: it edits ~/.zshrc directly, chezmoi reverts that on the next apply, and the warning comes back. Restart Docker Desktop after the first apply and it usually clears |
| Orange "this can prevent Docker from starting" banner on the Docker Engine page | A standing caution above the JSON editor, not an error — it warns that bad JSON here stops the daemon. The builder-GC config shown is Docker Desktop's stock default; Groundwork doesn't manage Desktop's daemon.json (work machines get equivalent disk-growth caps through the Colima template instead) |
| Docker Desktop badged "Personal" on a work machine | Worth fixing: Desktop needs a paid license in orgs over 250 staff or $10M revenue, which is why Groundwork's work profile installs Colima + the CLI instead. Either get a licensed seat through your employer, or re-run chezmoi init answering "work machine: yes", apply, and use colima start — the CLI workflow is identical |
#Karabiner (keyboard)
Caps Lock → Esc/Ctrl won't work until macOS trusts Karabiner. It needs two approvals that only you can click:
Approve Input Monitoring for Karabiner, and allow its driver / system extension (a separate prompt, sometimes under "Login Items & Extensions"). After both, quit and reopen Karabiner-Elements. A script cannot grant these — macOS requires a human click.
After Groundwork installs or updates Homebrew casks, it reopens the always-on apps — Karabiner-Elements, Raycast, BetterDisplay, and Anybox — if any is installed but not running, because Homebrew quits an app to replace it and never relaunches it. For Karabiner that makes the menu-bar icon, driver status, and any macOS permission prompts visible instead of leaving the keyboard rule silently inactive; for the others it keeps the launcher hotkey, display presets, and bookmark shortcuts answering. If macOS shows an "open an app downloaded from the internet" prompt, approve it only when it names one of these apps from your Applications folder.
If chezmoi apply mentions .config/karabiner/karabiner.json, the usual cause is Karabiner writing device or profile metadata after Groundwork last touched the file. Current Groundwork merges the Caps Lock rule into Karabiner's existing JSON instead of replacing the whole file, so pull the latest repo and re-run chezmoi apply.
#Obsidian
| No vault to open | The setup now creates one at iCloud Drive/Obsidian/<name> on apply. If it's missing, re-run chezmoi apply, or make one in-app pointed at that folder |
| Plugins not showing | Restart Obsidian once after first launch so the auto-installed community plugins load, then enable them in Settings |
| Notes stuck as cloud placeholders | Right-click the vault folder in Finder → Keep Downloaded so iCloud keeps them local |
#Git keeps asking for a login
This is an access problem, not a setup bug. git pull / chezmoi update can't authenticate. The usual culprit is being logged into gh as the wrong account, or using an SSH remote without the matching key/account configured. First, see who you are:
$ gh auth status # is this the right account?
$ gh auth switch # choose another already-authenticated account
$ gh auth login --git-protocol https
$ gh auth setup-git --hostname github.com
Groundwork itself pulls from a public HTTPS remote by default. The credential helper is still baked into the git config because most developers use GitHub for their own repos, and chezmoi manages ~/.gitconfig. SSH is still fine when the Mac's SSH config clearly maps the right key to the right GitHub account.
#Still stuck?
Two reliable fallbacks. Re-apply from the source of truth, and if that doesn't do it, ask an agent — it can read the actual error and the config together:
$ chezmoi update # pull latest config + apply it
$ claude # then paste the error and ask
Every managed file can be regenerated from the repo, so you can experiment with fixes freely. Worst case, a fresh chezmoi apply resets the managed files to a known-good state.