Linking core.hooksPath to the tracked, sandbox-writable .githooks/ let a sandboxed command rewrite a hook that git commit/push then run unsandboxed. Copies in .git/hooks can't be changed from the sandbox. This also keeps existing .git/hooks scripts working and lets npm install succeed outside a git checkout. pre-merge-commit now names itself in errors; hook tests clean up, ignore GIT_* env and cover the installer; docs state what each layer does and doesn't stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.3 KiB
2.3 KiB
claude-playground
A tiny todo CLI that exists to try out Claude Code features.
npm install # once per clone, and after changing .githooks/: installs the git hooks
npm test
npm start -- add "try Claude Code"
npm start -- done 1
npm start -- ls
What's set up
| File | Claude Code feature |
|---|---|
CLAUDE.md |
Project memory, loaded into every session |
.claude/settings.json |
Shared permissions + a hook that syntax-checks .js files after every edit |
.claude/skills/changelog/ |
A custom skill — run /changelog |
.claude/agents/test-writer.md |
A custom subagent — "use the test-writer agent to…" |
Protecting main
Changes reach main only through a PR that a human merges. Three layers, strongest first:
- Gitea branch protection on
main(set in the repo settings on code.westerkam.org): the only layer that can't be bypassed from a clone. - Git hooks from
.githooks/, copied into.git/hooksbynpm install(scripts/install-hooks.sh).pre-pushrejects any push that would updatemain, however it's spelled.pre-commit/pre-merge-commitrefuse ordinary and merge commits whilemainis checked out; cherry-pick, revert and fast-forwards skip them, but pre-push still stops the result leaving the machine. The hooks are copied, not linked withcore.hooksPath, because Claude Code's sandbox can't write inside.git/: a sandboxed command can't rewrite the installed hooks, which matters becausegit commit/git pushrun outside the sandbox. Anyone can still skip them with--no-verify. - Claude Code deny rules in
.claude/settings.jsonforgit push … main: a quick early stop, but they match command text, so they're easy to get around.
Layers 2 and 3 live on your machine and can be bypassed by whoever runs the commands; only layer 1 can't.
Things to try
- "There's a bug in how ids are assigned after removing a todo — find it and fix it with a test."
- "Use the test-writer agent to cover
src/todo.js." - "Add a
--jsonflag tols." then/code-review /changelogafter a few commits- Press
shift+tabto cycle permission modes (e.g. plan mode before a bigger change) /memoryto edit CLAUDE.md,/agentsand/hooksto inspect the setup