dotclaude

agent
Security Audit
Fail
Health Warn
  • License — License: NOASSERTION
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Fail
  • rm -rf — Recursive force deletion command in install.sh
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Global Claude Code config, synced between machines by one script: CLAUDE.md, writing rules, vendored skills and the bash environment commands run in, symlinked into ~/.claude and wherever Codex and other agents look

README.md

dotclaude

██████╗  ██████╗ ████████╗ ██████╗██╗      █████╗ ██╗   ██╗██████╗ ███████╗
██╔══██╗██╔═══██╗╚══██╔══╝██╔════╝██║     ██╔══██╗██║   ██║██╔══██╗██╔════╝
██║  ██║██║   ██║   ██║   ██║     ██║     ███████║██║   ██║██║  ██║█████╗
██║  ██║██║   ██║   ██║   ██║     ██║     ██╔══██║██║   ██║██║  ██║██╔══╝
██████╔╝╚██████╔╝   ██║   ╚██████╗███████╗██║  ██║╚██████╔╝██████╔╝███████╗
╚═════╝  ╚═════╝    ╚═╝    ╚═════╝╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝

License: MIT
Platform
Shell: bash

My global Claude Code config, kept in one place so a new machine takes a clone and a script instead of an afternoon of remembering. The same files are linked where Codex and other agents look, so one repo configures all of them. See Other agents.

install.sh symlinks everything here into $HOME. Editing a file in place edits the repo, so there is no copy step and nothing to forget to commit.

~/dotclaude/                            ~/
├── CLAUDE.md ─────────────────────────▶ .claude/CLAUDE.md, {.agents,.codex}/AGENTS.md
├── unslop.md ─────────────────────────▶ {.claude,.agents,.codex}/unslop.md
├── bashrc ────────────────────────────▶ .bashrc
├── skills/
│   ├── <skill>/ ──────────────────────▶ {.claude,.agents,.codex}/skills/<skill>
│   └── NOTICE.md
├── agents.conf
└── install.sh
Repo file What it is
CLAUDE.md How I want Claude to work: what counts as evidence, how to change code, how to talk to me
unslop.md Writing rules for every reply. Linked beside CLAUDE.md, which tells the agent to read it
bashrc The shell Claude runs commands in. See Why there's a bashrc in here
skills/ The agent skills, vendored and locally modified. See Skills
agents.conf Which agents the links go to. See Other agents
install.sh Links, installs, and writes the per-machine settings. Safe to re-run

Setup on a new machine

Two things have to be there first: git, and a package manager for the rest. That means Homebrew on macOS, and apt, dnf or pacman on Linux. Without one the script still links everything, but it installs no tools, and on macOS it finds only the preinstalled bash 3.2, warns, and leaves the shell setting unwritten.

git clone https://github.com/danjdewhurst/dotclaude.git ~/dotclaude
~/dotclaude/install.sh

In order, the script:

  1. Creates the agent directories agents.conf lists, moves anything already at a target path to <name>.bak (timestamped if a .bak is already there), and links everything into place.
  2. Installs the tools Claude leans on from Bash, in the table below.
  3. Finds the newest bash 4+ on the machine and writes its path into ~/.claude/settings.json, with an alias in your login shell as the fallback. See Why the bash path is written per machine.
  4. Merges autoMemoryEnabled: false into the same file and strips a hook older setups left behind. See Why settings.json isn't in here.
Tool What Claude uses it for macOS Linux
git Everything. Already there, since you cloned this Homebrew apt / dnf / pacman
rg Searching code without drowning in dependencies Homebrew apt / dnf / pacman
fd Finding files Homebrew apt / dnf / pacman
jq Reading JSON, and the settings merge above Homebrew apt / dnf / pacman
mise Language runtimes, and a fallback installer Homebrew mise.run
ast-grep Structural search by syntax rather than regex Homebrew mise
yq Reading YAML Homebrew mise

On a distro with none of those package managers the script says what it couldn't install and carries on.

Run it as many times as you like. A second run installs nothing and rewrites nothing.

Tested on macOS 26 (Apple Silicon), Ubuntu 24.04 and Fedora 41, including the no-root, no-sudo and no-package-manager paths.

Day to day

Edit ~/.claude/CLAUDE.md as normal, then:

git -C ~/dotclaude commit -am "tweak the debug-spiral rule"
git -C ~/dotclaude push

On the other machine, git -C ~/dotclaude pull. Claude reads CLAUDE.md at session start, so the next session picks it up with no restart dance.

Adding a skill is a directory under skills/ and a re-run of install.sh. Dropping one is the reverse: delete the directory, re-run, and the links go with it.

Why there's a bashrc in here

Left to itself, Claude Code runs its Bash tool in whatever $SHELL says, which for me is zsh. That works, but I'd rather the tool that runs commands on my behalf used bash. It's what the commands Claude writes assume, and it keeps its environment separate from the one I've spent years customising for typing.

Switching means bash needs config of its own, and the obvious shortcut is a trap. My first attempt was one line, source ~/.zshrc from ~/.bashrc. That hands zsh syntax to bash, which produces about sixty lines of autoload: command not found, prezto refusing to load with old shell detected, and mise's hooks failing on $+functions[...]. Worse, a stray echo in that file printed into the top of every command's output.

Hence a real bashrc here rather than a redirect to the zsh one.

bashrc is deliberately dull: Homebrew, mise, PATH, nothing interactive. No aliases, no zoxide, no completions. Those belong in ~/.zshrc, which is not synced, because they only matter when a human is typing.

The last line sources ~/.bashrc.local if it exists. That is where anything machine-specific goes: a tmux auto-attach, a PATH entry for a tool only one box has, an override of something set above it. It runs last, so it wins. Nothing breaks if the file is absent, and I never commit it here.

It is not optional. Homebrew on Apple Silicon lives at /opt/homebrew/bin, which is not on the default PATH. Without brew shellenv a fresh machine hands Claude a shell with no node, no php, no rg, and no clue why.

Why the bash path is written per machine

Claude Code reads the Bash tool's shell from env.CLAUDE_CODE_SHELL in settings.json, and only falls back to $SHELL and a scan of /bin, /usr/bin, /usr/local/bin and /opt/homebrew/bin when that key is missing or does not point at a runnable bash or zsh. So that key is the thing to set. Its value is different on every machine:

Machine bash lives at
macOS, Apple Silicon /opt/homebrew/bin/bash
macOS, Intel /usr/local/bin/bash
Linux /usr/bin/bash
macOS, preinstalled /bin/bash, still 3.2.57. Avoid

One synced file cannot hold all four. So install.sh finds the newest bash 4+ on the machine and merges the path it found into ~/.claude/settings.json:

{
  "env": {
    "CLAUDE_CODE_SHELL": "/opt/homebrew/bin/bash"
  }
}

The same path also goes into a marked block in ~/.zshrc and whichever bash login file already exists, as an alias setting $SHELL for that one command. That is the fallback for a machine where the key could not be written, one with no jq or a settings.json whose JSON is broken. It prefers ~/.profile or ~/.bash_login over creating a ~/.bash_profile, which would shadow them:

# >>> dotclaude >>>
# Point Claude Code's Bash tool at bash instead of the login shell.
alias claude="SHELL='/opt/homebrew/bin/bash' claude"
# <<< dotclaude <<<

Those are the shells you launch claude from, never the synced ~/.bashrc. Re-running rewrites the block where it already sits rather than stacking a second copy or moving it to the end of the file. If the block is already correct it does nothing at all. It writes through a symlinked rc file instead of replacing the symlink, which matters if your ~/.zshrc points into prezto or another dotfiles checkout. If the markers are damaged, the script leaves the file alone and warns.

Why settings.json isn't in here

~/.claude/settings.json is a real file on each machine, not a symlink out of this repo. What goes in it is machine-specific: MCP servers denied by UUID, notification channel, effort level. Syncing one copy across machines hands every machine another machine's answers.

install.sh merges two keys into it: env.CLAUDE_CODE_SHELL, for the reason in the previous section, and autoMemoryEnabled: false, because I want Claude reading CLAUDE.md rather than notes it wrote to itself. The merge goes through jq, so every other key survives, and the first change to a file that was already there leaves a settings.json.dotclaude.bak alongside. If there is no file yet it writes a minimal one holding just what it owns, with no backup, since there was nothing to back up. If the file is there but the JSON is broken it says so and changes nothing. The same pass strips a leftover SessionStart hook that used to inject unslop.md, now that CLAUDE.md tells the agent to read that file.

It also repairs one legacy case. A machine set up before this split still has ~/.claude/settings.json symlinked into the repo, pointing at a file git has since deleted. The installer converts that link back into a real file. It takes the repo copy if that is still on disk, and the last commit that carried it if it is not. That machine keeps the settings it was already running instead of a dangling link and Claude Code's defaults.

Skills

Three skills are in here: grilling, tdd and writing-for-agents. All of them, and unslop.md, started as other people's work, vendored and then locally modified. skills/NOTICE.md lists each one's upstream and licence. All MIT.

install.sh links each skill straight into the skills/ directory of every agent in agents.conf: ~/.claude/skills, where Claude reads them, plus ~/.agents/skills and ~/.codex/skills by default. Committing the content means the same bytes on every machine and nothing to install first. There is no lock file and no skills CLI in the loop, because every skill here carries local edits a CLI update would stomp. Updates are manual: diff a skill against its upstream and merge by hand.

Dropping a skill from skills/ here removes all of its links on the next install.sh. Nothing else in those directories is touched, so skills Codex installed for itself sit untouched next to the linked ones.

unslop.md is not a skill. install.sh links it beside CLAUDE.md in each agent directory, and CLAUDE.md tells the agent to read it before writing anything I'll see.

Other agents

The same config goes where other agents read it, and which agents that is lives in agents.conf rather than the script. Each entry is <dir>:<filename>: the directory is created, CLAUDE.md is linked into it under that filename, unslop.md is linked beside it, and the skills land in its skills/ subdirectory. The shipped list is Claude Code, the shared ~/.agents directory, and Codex:

AGENT_DIRS=(
  "$HOME/.claude:CLAUDE.md"
  "$HOME/.agents:AGENTS.md"
  "$HOME/.codex:AGENTS.md"
)

To change the list on one machine, put the same syntax in ~/.dotclaude.local. The script sources it after agents.conf, so it wins, and like ~/.bashrc.local it never gets committed here. AGENT_DIRS+=("$HOME/.gemini:GEMINI.md") adds an agent, redefining the array replaces the list. Removing an entry stops the linking but leaves the links already on disk. Delete those by hand.

One caveat. CLAUDE.md is written for Claude Code, so a few lines override that harness's defaults, the commit trailer for one, and mean nothing to an agent reading it as AGENTS.md. That's the trade for one file instead of one per agent, made knowingly.

Not synced

projects/, settings.json, and anything else under ~/.claude this repo doesn't link stay machine-local. So do ~/.zshrc, ~/.bashrc.local and ~/.dotclaude.local. That includes any skill sitting in an agent's skills/ directory without being in skills/ here. The work-specific ones stay off this public repo on purpose.

Known rough edges

Neither ast-grep nor yq is packaged for apt, dnf or pacman, so on Linux they come from mise, which carries both. Homebrew has them, so macOS uses that and only falls back to mise if the brew install fails. Debian ships fd-find with its binary named fdfind, so the script drops an fd symlink in ~/.local/bin to match what Claude expects.

install.sh replaces ~/.bashrc wholesale instead of merging it. On Ubuntu that means losing the distro default's history settings and colour prompt in your own interactive bash sessions. It keeps the original as ~/.bashrc.bak.

The alias only affects interactive shells, so claude launched from a script, a cron job or an editor task never sees it. Those runs rely on the settings.json key, which Claude Code reads however it was started.

On a machine without Homebrew the script installs mise with curl https://mise.run | sh. That's the vendor's documented method, but it is still piping a remote script into a shell. Swap it for their apt repo if that bothers you.

If you found this

It's my config, not a template. CLAUDE.md is written in first person about how I want to be worked with, and bashrc assumes my toolchain. Fork it and rewrite both rather than copying them and wondering why Claude keeps mentioning mise.

MIT licensed, except the vendored skills and unslop.md. Those belong to their authors under their own MIT terms, listed in skills/NOTICE.md. Take whatever's useful.

Reviews (0)

No results found