Hooks & skills¶
This repository ships a Claude Code settings.json, a theme, and
two worktree-lifecycle hooks. Both hooks point at the same scripts the manual
start-tmux-session /
end-tmux-session wrappers call, so the naming
convention stays in lockstep regardless of who triggered the worktree. No user-level
skills/ directory is checked in. The .claude/plans/
directory is present but is a runtime artifact for plan mode — not configuration.
Hooks¶
Claude Code looks for hooks in ~/.claude/hooks/ (global) and <repo>/.claude/hooks/
(project-scoped). Both are picked up automatically once a matching event is declared in
settings.json. Upstream docs:
docs.claude.com/en/docs/claude-code/hooks.
WorktreeCreate¶
Registered in settings.json:
"hooks": {
"WorktreeCreate": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/start-worktree" }] }
]
}
start-worktree runs whenever Claude Code creates a worktree, and is
also invoked directly by start-tmux-session when a
worktree-name argument is passed. It:
- Picks the repo path from
$1, falling back to$CLAUDE_PROJECT_DIR, thengit rev-parse --show-toplevel. Its basename becomes the worktree's base name. - Picks a suffix from
$2, falling back to anamefield in JSON on stdin (the hook protocol), then a UTCYYYYMMDD-HHMMSStimestamp. - Sanitises both the base name and the suffix (anything outside
[A-Za-z0-9._-]becomes-), joins them as<base>-<suffix>, and creates the worktree at~/.cache/agent/worktrees/<name>on a branchagent/<name>. If the branch already exists, it's reused instead of recreated. - Prints the worktree path to stdout (Claude Code consumes it) and logs progress to the
controlling tty using
tputcolours.
The worktree root (~/.cache/agent/worktrees/) is XDG cache by design — worktrees are
throwaway work areas, not configuration. The path is hard-coded in the script because it
has to match the literal path in the includeIf "gitdir:…" block
that loads agent.gitconfig, and git's includeIf can't expand
environment variables. Bonus: ~/.cache is already in the sandbox
filesystem.allowRead/allowWrite lists, so no separate sandbox
extension is needed.
The agent/ branch prefix is deliberate: it's the signal
end-worktree uses to decide a branch is safe to delete.
WorktreeRemove¶
Registered in settings.json:
"hooks": {
"WorktreeRemove": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/end-worktree" }] }
]
}
end-worktree runs when Claude Code finishes with a worktree, and is
also invoked per selection by end-tmux-session. It:
- Reads the worktree path from
$1, falling back to aworktree_pathfield in JSON on stdin (the hook protocol). - Exits cleanly if the path is already gone (
! -d), so re-runs and manual cleanups don't error. - Captures the current branch and the parent repo (via
git rev-parse --git-common-dir) inside the worktree before anything is destroyed. - Warns to the TTY if the worktree has uncommitted changes (
git status --porcelain) or unpushed commits (git rev-list --count HEAD --not --remotes— counts commits reachable from HEAD but absent from every remote ref). The warning doesn't block removal — the loud notice gives you a chance to recover lost commits from the reflog within ~30 days. - Kills the tmux session whose name matches the worktree directory's basename, if one exists. With the unified naming convention that's the session created for this worktree.
- Removes the worktree (
git worktree remove --force), swallowing failures so a half-broken worktree doesn't block the hook. - Deletes the branch (
git branch -D) only if it starts withagent/— the prefixstart-worktreeuses. Branches with any other prefix are left in place.
The agent/-prefix guard is intentional: branch deletion is destructive, so the hook only
touches branches it can identify as throwaway agent state.
Claude-is-waiting indicator¶
Five hooks drive a "Claude is waiting for input" indicator, all pointing at the same leaf
script agent-tmux-status with a state argument
(opencode drives the very same script from its
status-indicator plugin):
"hooks": {
"Stop": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/agent-tmux-status waiting" }] }
],
"Notification": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/agent-tmux-status attention" }] }
],
"PostToolUse": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/agent-tmux-status clear" }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/agent-tmux-status clear" }] }
],
"SessionEnd": [
{ "hooks": [{ "type": "command", "command": "~/.local/share/scripts/agent-tmux-status clear" }] }
]
}
| Event | Arg | When it fires | Look |
|---|---|---|---|
Stop |
waiting |
Claude finished a turn and is awaiting input | peach ● |
Notification |
attention |
Claude needs permission or attention | bold red |
PostToolUse |
clear |
A tool ran after you approved it — Claude resumed | cleared |
UserPromptSubmit |
clear |
You submitted a reply — Claude is busy again | cleared |
SessionEnd |
clear |
Session ended — don't leave the flag stuck | cleared |
Notification is the louder of the two because a permission prompt actively needs you, whereas
Stop just means it's your turn. Notification also fires after ~60s idle, so a long-idle
waiting naturally escalates to attention.
The two clear triggers cover the two ways Claude resumes from an attention state. The
idle-timeout Notification clears via UserPromptSubmit (you reply by typing). A permission
Notification resumes differently: you approve, and the approved tool runs — there's no prompt
submission, so without PostToolUse the red would stay stuck until the next Stop.
PostToolUse fires right after the approved tool executes (the first moment Claude is working
again), clearing that gap. It fires after every tool, but the script is a no-op-safe single
tmux unset, so the only visible effect is the post-approval clear.
The script branches on $TMUX: inside tmux it stores the state token in a per-window
@agent_status option that theme.conf maps to a colour
and glyph; outside tmux it falls back to an OSC 0 terminal title. See
scripts/tmux → agent-tmux-status for the details.
Skills¶
Same story for skills — none ship in this repo. The default Claude Code install provides
several built-in skills (init, review, security-review, etc.); they're discovered
automatically and don't need to be checked in.
Project-specific skills go in <repo>/.claude/skills/. See
Claude Code skills docs for the
declaration format.
Plans¶
.claude/plans/ (excluded from stow via the conventions in stow)
holds plan files written by Claude Code while in plan mode. The directory is intentionally
gitignored at the repo level via the matching .gitignore entry — plans are session
artifacts, not configuration.
Adding more¶
Additional hooks or skills belong on this page. Add a fresh subsection alongside
WorktreeCreate and link to the script from
settings.json.