hosts: localhost requires implicit inventoryUsing hosts: all breaks the implicit localhost that Ansible provides when no inventory file exists. Stick with hosts: localhost and connection: local. The --start-at-task flag works fine with this setup when run as ansible-playbook playbook.yml.
homebrew/cask tap is deprecatedModern Homebrew no longer requires tapping homebrew/cask. The community.general.homebrew_tap module will fail if you try to add it. Just list casks directly in homebrew_casks — Homebrew resolves them automatically.
If a Homebrew package is from a third-party tap (e.g. hashicorp/tap/vault), the tap must be added in a separate task before the Install Homebrew packages task. The homebrew module does not auto-tap.
community.general.git_config rejects empty valuesSetting credential.helper to "" via the git_config module fails validation. Use ansible.builtin.command: git config --global credential.helper '' instead.
failed_when: false is easy to loseWhen restructuring tasks, failed_when: false on check-commands (like pgrep) can get accidentally dropped. This causes the playbook to abort when the checked service isn't running. Always verify check-tasks retain their failed_when: false after refactors.
Python 3.14 (from brew install python@3) can break pip install -e . with hatchling if the project layout is a single file (e.g. server.py) instead of a package directory. Workaround: install dependencies directly (pip install "dep1" "dep2") instead of installing the project.
Group all tasks requiring human interaction (permissions, auth, service starts) at the top. Check each condition, collect results, and print a single consolidated action summary. All automated tasks go in Phase 2. This way:
lookup('env', ...)MCP server configs need API keys. Rather than hardcoding them, the playbook reads them from the shell environment using lookup('env', 'VAR_NAME'). This requires source exports.sh before running the playbook. The keys get baked into .mcp.json at write time.
macOS Git defaults to credential.helper=osxkeychain, which will use stored personal GitHub credentials. On a machine that should only authenticate via GitHub App tokens (GH_TOKEN), disable it: git config --global credential.helper ''.
apps/mcp-servers/Each server is a self-contained project (Node.js with TypeScript, or Python with pyproject.toml). The playbook handles:
.mcp.json with all server configs@playwright/mcp is installed as a global npm package, not in the repo. Chromium is installed separately via npx playwright install chromium. The .mcp.json entry uses npx @playwright/mcp to launch it.
A custom MCP server wrapping macOS screencapture -x gives Claude the ability to see the desktop. Requires screen recording permission for iTerm2 (System Settings > Privacy & Security > Screen Recording). This cannot be granted programmatically due to SIP.
The analytics-mcp package is installed via pipx (not pip) to keep it isolated. It needs GOOGLE_APPLICATION_CREDENTIALS pointing to gcloud application default credentials, which require an interactive browser login.
Per-repo .claude/settings.json hooks are a security risk — a malicious repo can override your protections. Safety hooks belong in ~/.claude/settings.json (global, user-level) where only Ansible controls them.
rm -rf /, force push, curl | bash, fork bombs, etc..env, SSH keys, AWS creds, kubeconfiglogs/claude-audit.jsonlThe audit hook runs async: true so it doesn't slow down Claude.
When a hook blocks (exit 2), Claude sees the stderr message and adapts. It will rephrase the command or try a different approach. This is by design — hooks are guardrails, not walls.
This machine uses PericakAI (Pai) / [email protected] for git commits, not the personal kylep / [email protected] identity. The bootstrap script fails hard if GitHub App credentials aren't available — no fallback to interactive gh auth login which would authenticate as the personal account.
~/CLAUDE.md is written by the playbook and must be updated there. It contains the identity rules, security posture (bypass permissions warning, no curl | bash), and the self-referential instruction to always update the playbook when changing it.
dockutil (brew package) provides CLI control over macOS Dock items. The playbook checks current Dock state and only modifies it if it doesn't match the desired list. This avoids unnecessary Dock restarts on re-runs.
dockutil --remove all fails on empty DockIf the Dock is already empty, dockutil --remove all returns an error. The idempotency check prevents this by skipping the remove/add cycle when the Dock already matches.