Reference Β· Command guard Β· Skills Β· License
My Claude Code configuration: rules, subagents, hooks and permissions for a backend-heavy stack (Kotlin with Ktor, Python with FastAPI, MongoDB, Docker on a small VPS) with a React and Tailwind frontend on the side. It is the ~/.claude directory I use every day, cleaned up so it can be shared.
Note
I'm publishing this to be read and borrowed from, not installed as it is. The permissions, plugin choices and some paths are mine, and you will want your own. It is an independent personal project and is not affiliated with or endorsed by Anthropic.
I work on two machines: a Windows desktop at home and a MacBook when I'm on the go. I wanted Claude Code to behave exactly the same on both. Same rules, same subagents, same permissions, same hooks, without rebuilding anything by hand and without wondering why a command is allowed on one machine and blocked on the other.
So the whole ~/.claude directory is a git repository. A hook pulls it when a session starts, and another one commits and pushes it when the session ends. If I change a rule on the desktop, the MacBook has it the next time I open Claude Code.
flowchart LR
D["Desktop at home"] <-->|"session start: pull<br/>session end: push"| R[("Private config repo")]
R <-->|"session start: pull<br/>session end: push"| M["MacBook on the go"]
Two things followed from that:
- One setup, two operating systems. The hooks are plain Node scripts that never go through a shell, and the permission rules exist for both the Bash and the PowerShell tool, so nothing depends on which machine I'm sitting at. I run it on both, and the hooks, the sync and the notification sound all work on each.
- An unattended push has to be safe. A hook that publishes on its own is only as safe as what it is allowed to publish. The
.gitignoreis a whitelist (everything is ignored unless I list it), and every auto-sync commit goes through a secret scan first.
This repository is a cleaned copy of that private one, without my skills and without anything personal.
π CLAUDE.md |
Short global preferences: stack, tooling, how I want GitHub handled. |
π rules/ |
Ten rules. Five general ones (including web-research escalation) are always loaded; five are per-stack (Python, Kotlin, MongoDB, Docker, frontend) and load only when a matching file is touched. |
π€ agents/ |
Nine subagents: planner, architect, code and security reviewers, a test-first guide, and a few for builds, cleanup, E2E and docs. |
β¨οΈ commands/ |
Eleven slash commands, such as /plan, /verify and /build-fix. |
πͺ scripts/ |
Node hooks that guard commands, scan for secrets, format edited files and keep the config synced. |
βοΈ settings.json |
Permissions (deny, ask, allow), hook wiring, plugins and model settings. |
Keep the always-loaded part small. CLAUDE.md and the five general rules come to about 2.3k tokens. Anything stack-specific lives in a rule with a paths: list in its frontmatter, so the Python rules cost nothing while I'm working on a Kotlin service.
Write hooks in Node, without a shell. The same scripts run on Windows and macOS. bash-guard.js blocks the usual disasters (force pushes, recursive deletes of root-like paths, curl | sh, dropping a database) and scans the staged changes for secrets before every git commit. It reports file, line and pattern, never the value. How it works.
Make auto-sync fail closed. Because the session-end commit would bypass the guard above, secret-gate.js runs the same scanner first. If it finds something, or breaks, the sync is skipped and nothing leaves the machine.
Order permissions as deny, ask, allow. Destructive and outward-facing commands ask first, routine safe ones run without a prompt, and secrets are off limits. Pattern matching on command text is not a security boundary, which is why the hook above exists as a second layer.
Pick the model per job. Opus for planning and architecture, Haiku for documentation, Sonnet for everything else, using aliases instead of pinned model IDs so the setup does not go stale.
flowchart LR
S([Session starts]) -->|sync.sh| P[Pull the config repo]
P --> W[Working session]
W -->|Bash or PowerShell command| G[bash-guard.js]
W -->|Edit or Write| F[format-on-edit.js]
W -->|Prompt or idle| N[notify.js]
W --> E([Session ends])
E -->|push.sh| SG[secret-gate.js]
SG -->|clean| C[Commit and push]
SG -->|secret found| K[Skip the sync]
This is the output of bash-guard.js, rendered from a real run. Each command is sent to the hook the way Claude Code would send it, and the third one commits a file that contains a fake token.
The message goes back to Claude, so it usually reacts by choosing a safer command or asking me, instead of retrying the same one.
Text version of the same run
$ git push --force origin main
Blocked by bash-guard: force push.
Force pushes rewrite shared history. Ask the user to run it themselves.
[exit 2]
$ rm -rf ~
Blocked by bash-guard: recursive delete of a root-like path.
Delete a specific subdirectory instead.
[exit 2]
$ git commit -m "add config"
Blocked by bash-guard: possible secrets in the changes to be committed (values are not shown):
- config.py:1 GitHub token
Remove them, load them from environment variables (Phase/.env), and rotate any secret that was ever pushed.
For an intentional test fixture, add the comment "secret-scan:allow" on that line.
[exit 2]
$ git status
[exit 0]
.
βββ CLAUDE.md global preferences, always loaded
βββ settings.json permissions, hooks, plugins, model
βββ rules/ general rules, plus per-stack rules scoped with paths:
βββ agents/ subagents
βββ commands/ slash commands
βββ scripts/
β βββ hooks/ bash-guard, secret-gate, format-on-edit, notify, sync, push
β βββ lib/ shared helpers and the secret scanner
βββ docs/
β βββ assets/ banner and terminal image
β βββ bash-guard.md how the command guard and secret scan work
β βββ reference.md every rule, agent, command and hook in detail
βββ skills/ list of the skills I use (not included)
Read before you copy. The rules and agents are the easiest parts to reuse; start there and cut whatever does not match your stack.
Try the guard before you register it. Run this in a normal terminal, not through Claude with the hook enabled, because the guard would block the command that contains the test string. It should print 2:
node -e "
const { spawnSync } = require('child_process');
const command = 'git push --' + 'force';
const r = spawnSync('node', ['scripts/hooks/bash-guard.js'], { input: JSON.stringify({ tool_input: { command } }), encoding: 'utf8' });
console.log(r.status);"Warning
sync.sh and push.sh are written for my own setup and will not work for you as they are. They hardcode my private config repository, the branch master and the directory $HOME/.claude. Change those variables or leave the two hooks out of settings.json. push.sh commits and pushes everything your .gitignore allows, so keep the whitelist and the secret gate in place if you use it.
The full details, including the permission lists, the hook behaviour and how I set up a new machine, are in docs/reference.md.
My own work here is released under the MIT License. It comes with no warranty, so read any hook that runs commands or pushes to git before you enable it.
Parts of agents/ and commands/ started from everything-claude-code by Affaan Mustafa, which is also MIT licensed. Its notice is kept in THIRD_PARTY_NOTICES.md. The skills I use belong to their authors and are not included; skills/README.md lists them with their licenses.