Files
dots/common/.config/claude/README.md
T
2026-09-17 20:58:21 +02:00

11 KiB

Claude Code config

Portable Claude Code customizations, synced via these dots. Claude Code still uses its default ~/.claude/ directory; we surface the tracked files below into it via per-file/dir symlinks, so edits made through Claude write straight back into this repo.

What's tracked here

  • settings.json — global settings: permissions (deny git commit/push, gh pr write ops, and reads of secret files), default model, enabledPlugins, vim mode, dark theme, effortLevel, fullscreen TUI, statusLine, hooks, worktree defaults, a generic autoMode.environment, … Nothing in here may name a specific project, path or remote: this file is published by the public mirror, and the auto-mode classifier only reads autoMode from user/managed settings anyway (never from a project's .claude/settings*.json).
  • statusline.py — rich status line (dir, git, model, context %, cost, disk — system metrics live in the tmux bar). See StatusBar.md for what each segment and colour means.
  • keybindings.json — custom keybindings: vim-style scroll/navigation, plus ctrl+x v for the verbose transcript view (app:toggleTranscript, same keystroke as opencode's <leader>v; the default ctrl+o still works, and ctrl+e inside the view expands everything).
  • CLAUDE.md — global user preferences applied to every project.
  • hooks/format.py — PostToolUse hook: formats edited files by extension (stylua / ruff / prettier / gofmt / rustfmt / fish_indent / shfmt / taplo). Every formatter is opt-in: stylua, ruff, prettier and taplo run only where the project has their config file (found walking up); gofmt and rustfmt gate on a project marker (go.mod / Cargo.toml); fish_indent and shfmt gate on an .editorconfig section that matches the file. Wired to Edit|Write and Bash — under auto mode Claude edits through the shell, so the Bash branch scans the command for path-like tokens and formats the ones modified in the last 30 s. No-ops if the formatter isn't installed, and always exits 0.
  • hooks/git-guard.py — PreToolUse hook on Bash: structurally blocks anything that commits, rewrites history or discards working-tree changes — git commit / push / revert / cherry-pick / rebase / merge (not --abort), clean / reset in any form that moves HEAD or unstages everything (reset -- <paths>, reset HEAD <path> stay allowed) / restore <paths> (--staged <path> is fine, --staged . is not) / checkout -- / checkout . / stash drop|pop|clear / branch -D|-f|-M, and gh pr merge / create / close / edit — wherever the flags sit (git -C . push, /usr/bin/git push, sh -c 'git push'), which the prefix-matched deny rules can't. The discard group is what protects parallel sessions sharing one checkout. Exit 2 + a one-line reason. Heredoc bodies are skipped on purpose (file contents). Regression matrix: bash ~/.claude/hooks/git-guard-test.sh (a file on purpose — typed inline, the commands under test would trip the live hook).
  • hooks/notify.py — Notification hook (idle_prompt|permission_prompt): notify-send desktop notification when Claude is waiting on you; no-op on a headless host.
  • hooks/drift-check.sh — SessionStart + UserPromptSubmit hook, read-only: fails loudly when a ~/.config/claude/* file has broken out of the repo into a plain file, or ~/.claude/* no longer resolves into the dots repo. That is what Claude Code's atomic settings rewrite does through the stow layer — on 2026-09-15 it silently reverted the live config to a stale snapshot for an hour, dropping four hook events. At session start the warning is shown to you (exit 2, session goes on); on each prompt any drift is fed to Claude as context so it reports it. Fix is still link.sh (heals identical copies, warns otherwise).
  • hooks/local-commands.py — UserPromptExpansion hook: TUI-side slash commands. When you type /x and skills/x/local.py exists, the hook runs it with the arguments, shows its output to you and blocks the expansion (exit 2), so no prompt reaches the model and it costs nothing. Skills without a local.py expand as usual. Caveat baked into the design: the hook fires after the skill body expanded, so a skill with a local.py must not also act via a ! inline command.
  • skills/ — custom Agent Skills. See Skills below.
  • link.sh — idempotent bootstrap that creates the ~/.claude symlinks below.
  • ../git/claude-commit-msg + ../git/hooks/prepare-commit-msg — the commit-message handoff. A session pipes its suggestion into claude-commit-msg save, which stores it as its own file under $(git rev-parse --git-path CLAUDE_COMMIT_MSG.d)/ with the paths it staged; on the next git commit / lazygit C the hook prefills the editor with the message(s) whose paths are part of that commit and consumes them (used/). Parallel sessions therefore never overwrite each other, and committing one session's files gets that session's message. claude-commit-msg list shows what is pending. The old single CLAUDE_COMMIT_MSG file is still honoured. Both tracked here (deployed to ~/.config/git/); the hook is installed per repo — see setup step 4 — and deliberately not via a global core.hooksPath, which would override a project's own hooks.

The settings.json hook commands for the newer scripts are wrapped in [ -f … ], so a host that has pulled the repo but not yet re-stowed just skips them instead of blocking every Bash call.

Paths inside settings.json reference ~/.claude/... via $HOME/PATH (/usr/bin/env python3 ~/.claude/...), so they work regardless of the username, python location, or where the dots repo is cloned (~/.dots, …).

What is not tracked (stays local, per device)

Everything else under ~/.claude/ is machine-specific or secret and must never be committed: credentials (.credentials.json), session history, projects/ (transcripts + memory), caches, and the plugins/ cache/binaries.

Setup on a new device

# 1. Deploy the dotfiles repo — install.sh stows ~/.config/claude AND runs link.sh for you
cd ~/.dots && ./install.sh

# 2. Launch Claude and log in once (credentials are NOT synced)
claude

# 3. Reinstall the plugins from settings.json -> enabledPlugins
#    (typescript-lsp, frontend-design — anthropics/claude-plugins-official) via /plugin.
#    Optional: install any formatters you want the hook to use (stylua, ruff, prettier, …),
#    and libnotify (notify-send) on GUI hosts for hooks/notify.py.
# 4. In every repo you commit in: install the one-shot commit-message prefill hook
ln -sf ~/.config/git/hooks/prepare-commit-msg (git rev-parse --git-path hooks)/prepare-commit-msg

install.sh deploys ~/.config/claude (via stow) and then runs link.sh automatically. You only need to run link.sh by hand (bash ~/.config/claude/link.sh) if a link later gets clobbered (e.g. Claude's /config replaces one with a plain file) — it's idempotent and self-healing.

A new skill/hook added to the repo shows up after a re-stow — ./install.sh (or dotsync) links the new file into ~/.config/claude, which link.sh then surfaces into ~/.claude.

Skills

Custom skills live in skills/<name>/SKILL.md (the directory name is the /command) and are linked in as ~/.claude/skills. All are disable-model-invocation: true — explicit / only, so Claude never auto-triggers them. Run them inside the repo whose branch you're working on.

  • /review-branch [base] — reviews the current branch's diff (vs its merge-base with the base branch) for 🐞 bugs / 🔒 security / optimizations / 📖 readability, reports grouped findings with file:line, then applies the readability + safe fixes after you approve. Never commits. Borrows /code-review's discipline: CLAUDE.md-aware, changed-lines-only, verified bugs with a false-positive filter (skips what linters/CI catch and pre-existing issues). Ends by pointing you at the relevant built-in follow-ups (/security-review, /code-review, /verify, …).
  • /pr-description [base] — auto-detects this repo's GitHub PR template (.github/pull_request_template.md, …), fills it from the branch diff + commits, prints a copy-paste markdown block, and copies it to the clipboard with wl-copy. Leaves verification checkboxes for you; never creates/pushes the PR.
  • /commit-msg [hint] — reads the staged diff, infers the repo's commit style from recent git log (e.g. this repo's [Scope] summary), drafts a matching message, prints it, and copies it to the clipboard with wl-copy. Never stages or commits — draft only.
  • /verbose [on|off|status] — flips the persisted verbose view for the current project: viewMode (verbose / default — the key that actually decides the view and beats a sticky /focus choice for new sessions) plus verbose and showThinkingSummaries, written to the project's .claude/settings.local.json (machine-local, never committed) rather than the tracked settings.json, so a toggle never shows up as a dots diff. Runs TUI-sidelocal.py via hooks/local-commands.py, no model turn — with the SKILL.md body as the fallback on a host where the hook isn't deployed. The live toggle is a keypress: ctrl+x v / ctrl+o.
  • /pr-loop [pr] — babysits the current branch's PR in a self-paced loop: fixes review comments that don't need your input — from any reviewer (Codex, Claude, humans) plus any @claude request — git adds them, drafts the commit message (/commit-msg style) and marks each thread with a reaction instead of an "Addressed — …" reply: 👍 fixed, 👀 parked for your decision, 👎 a bot false positive (bots only, always with a one-line why). Then pings you to commit & push and re-checks every ~10 min (via ScheduleWakeup). Resolves threads once their fix is pushed — dismissals right away, since there's nothing to land. Stops when no unresolved comments remain or the automated reviewers (Codex/Claude) hit their limit. Prep-only — never commits/pushes/merges; needs gh authed and the session left open. Each cycle it prints when it will re-check and that Esc (between cycles) stops it; on the last cycle it prints a clear "Finished — no more active issues" and does not reschedule.

Adding more config later

Drop the file/dir under .config/claude/ here, add its name to the items list in link.sh, and re-run bash ~/.config/claude/link.sh. For example commands/ or agents/.