Agentic Dev Setup 2026
New machine for agentic work. The highest-leverage single change takes a few minutes.
Why not just alias the classics to their modern rewrites?
Coding agents emit grep, find, and sed by reflex. Modern rewrites (ripgrep, bfs/fd, sd) are faster. But only two of the three alias cleanly: ripgrep handles most grep syntax natively, bfs is a drop-in find replacement, while fd and sd have incompatible CLIs and regex dialects. find . -name "*.ts" through fd errors out. sed 's/foo/bar/g' through sd silently produces wrong output when the pattern has BRE quantifiers or when the replacement uses & or backreferences.
The honest answer: alias the two that work, leave the third alone.
brew install ripgrep bfs
# in ~/.zshrc
alias grep='rg' # ripgrep handles most grep syntax natively
alias find='bfs' # bfs is a drop-in find replacement (breadth-first, faster)
- ripgrep: aliasable. Recursive by default, respects
.gitignore, handles the commongrepflags agents emit. - bfs: aliasable. Advertises drop-in GNU/BSD
findcompatibility, verified on-name,-type,-maxdepth,-exec, boolean operators. - sd and fd: worth installing, not worth aliasing. The speedups are real but the CLIs diverge too much from classic
sed/findto be safe aliases. Use them by name when you want modern syntax.
I built a sed dispatcher that routes literal substitutions to sd and falls back to real sed for anything with regex; the repo stays up as reference for the pattern, but the safe subset is too narrow to earn its maintenance, so I don’t alias it.
Replace BSD coreutils with GNU
macOS ships BSD date, ls, cp, readlink, stat, head, tail, etc. Agents trained on GNU emit date -d "yesterday", readlink -f path, stat -c %Y file, head --lines=10, none of which exist on BSD. Install coreutils and put the GNU versions on PATH:
brew install coreutils
# in ~/.zshrc, BEFORE any other PATH setup
export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
Same trick as the aliases above: classic names, modern (here, GNU) implementation. Unlike fd/sd, GNU coreutils is a superset-compatible drop-in. BSD scripts that use shared flags still work, and the GNU-only flags agents reach for now resolve.
Pairs naturally with findutils (GNU find/xargs/locate) and gnu-sed. For sed in particular, brew install gnu-sed + alias sed=gsed is the cleanest option if you don’t need BSD portability.
Block the destructive operations before they run
Agents move fast. A handful of Bash commands are catastrophic when the agent is wrong and the shell is permissive. Hooks block them first. Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "input=$(cat); cmd=$(echo \"$input\" | jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qF 'rm -rf'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"rm -rf is blocked for safety. Use mv /tmp/ instead.\"}}'; fi"
},
{
"type": "command",
"command": "input=$(cat); cmd=$(echo \"$input\" | jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qE 'git[[:space:]]+push.*(--force|-f)'; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Force push blocked. Use regular push or new branch.\"}}'; fi"
},
{
"type": "command",
"command": "input=$(cat); cmd=$(echo \"$input\" | jq -r '.tool_input.command'); if echo \"$cmd\" | grep -qE 'git[[:space:]]+commit.*--amend'; then echo '{\"systemMessage\":\"Warning: git commit --amend modifies history. Only use if commit hasn'\\''t been pushed.\"}'; fi"
}
]
}
]
}
}
Three guards:
- Block
rm -rf. Denied; agent is nudged towardmv /tmp/instead. The point is undoability. /tmp sticks around long enough for you to notice the mistake. - Block force push. Denied with reason.
- Block amend when pushed.
git commit --amendrewrites history. If HEAD is already on any remote, block: amending would force the next push to rewrite published history. If HEAD is local-only, allow silently (the pre-push fixup case). The sample JSON above only warns; the stricter version is a separate script that checksgit branch -r --contains HEAD:
if echo "$cmd" | grep -qE 'git\s+commit\s+.*--amend'; then
if git rev-parse --git-dir >/dev/null 2>&1; then
head=$(git rev-parse HEAD 2>/dev/null)
if [ -n "$head" ] && git branch -r --contains "$head" 2>/dev/null | grep -q .; then
echo "Block: HEAD is on a remote. --amend would rewrite published history." >&2
exit 2
fi
fi
fi
Gemini CLI with approval-mode=yolo
For agent-to-agent calls:
brew install gemini-cli google-cloud-sdk
Credentials (Vertex AI):
export GOOGLE_APPLICATION_CREDENTIALS="$HOME/atom.json"
export GOOGLE_CLOUD_PROJECT="your-project-id"
export GOOGLE_CLOUD_LOCATION="global"
export GOOGLE_GENAI_USE_VERTEXAI="true"
Auto-approve tool calls:
gemini() {
/opt/homebrew/bin/gemini --approval-mode=yolo "$@"
}
(--yolo is the older deprecated flag; --approval-mode=yolo is current.) Now gemini "prompt" auto-approves, useful when Claude invokes Gemini for second opinions or specialized tasks.
The audience here is agents helping humans, so I won’t write out what the agent already knows. For a checklist the agent can run against a fresh machine, verify these:
- Homebrew installed,
brew shellenvon PATH gitconfigured withuser.nameanduser.emailghauthenticated (gh auth status)- SSH key generated and added to GitHub
- Node (via nvm/volta or brew) + a package manager (pnpm/bun/yarn)
- Python:
uvfor greenfield,pyenv+poetryif the project requires pinned versions - Go (if the project uses it)
jqon PATH (the hooks depend on it)~/.claude/settings.jsonexists and references the hooks- Repo directory convention established (e.g.
~/Documents/,~/code/)
Read the hook scripts before relying on them. Any code you put in your shell is code that runs when you type.