claude-code-config
CLAUDE.md14 starsChanged 4 days agoMITView on GitHub
It is loaded as user-level memory regardless of working directory. Hostnames name each device's job; the same lowercase name is the macOS hostname, the Tailscale node, and the MagicDNS name. Call each device by its name, in prose too—almanac, not "the Mini"; sextant, not "the laptop". Hostnames are load-bearing: the notes vault's task update runs only when hostname is sextant, and the global Brewfile's (~/.config/homebrew/Brewfile) host case checks for almanac. A rename must update both. Tailscale takes a new name…
In this repository12 other files it gives its agents.
- .claude/CLAUDE.mdCLAUDE.md
- cc-release-reviewSkill
- verifySkill
- assisted-logSkill
- code-theorySkill
- code-walkthroughSkill
- downloads-triageSkill
- editorSkill
- release-gateSkill
- release-shipSkill
- release-sweepSkill
- rexSkill
The file
We split this CLAUDE.md into the rules it gives an agent
4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Use defaults until justified
- Very specific: line 25
Before recommending a change to a tool's config or keybindings, check the tool's actual default (its docs, source, --help, or a …
- Very specific: line 54
After a move or delete, check only the new path.
- Distinctive: line 69
Every open issue in a triage pass gets an explicit disposition (fix / close / defer with reason).
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) across all sessions for this user. It is loaded as user-level memory regardless of working directory.
About me
- Independent developer working on solo projects under the philoserf umbrella.
- Primary languages: Go and TypeScript.
Team working rules
- Yes, and…
- Name things once
- Embrace simplicity
- Ask permission once
- Assume good intentions
- One file until we need two
- Use defaults until justified
- Known and fixable gets fixed
- A stated preference ("I don't care about X") means remove X entirely—not a caveat or an accepted cost
Tool defaults
- Obsidian CLI: default to
vault=notesunless another vault is named. - Before recommending a change to a tool's config or keybindings, check the tool's actual default (its docs, source,
--help, or a default-config dump) instead of assuming it. - MCP connectors: the desired enabled and disabled state, and how to count usage on both Macs before acting on an "unused" finding, are in
references/connector-audits.md. Read it before touching a connector or acting on a/doctorfinding. - Browser automation: prefer the
safariMCP (Safari's built-in/usr/bin/safaridriver --mcp, since Safari 27.2) overclaude-in-chromefor navigating, screenshotting, or inspecting web pages. It drives the everyday Safari, so its automation windows sit beside normal browsing. Note it cannot emulateprefers-color-scheme. If it fails with a "remote automation" error, Safari needs Settings → Developer → Allow remote automation, then an MCP reconnect (/mcp).
Devices
Hostnames name each device's job; the same lowercase name is the macOS hostname, the Tailscale node, and the MagicDNS name. Call each device by its name, in prose too—almanac, not "the Mini"; sextant, not "the laptop".
sextant: MacBook, the daily machine (wasLaptop).almanac: headless Mac mini, the always-on host work is dispatched to (wasMini).moleskine: iPad.telemetry: iPhone.lantern: Apple TV, on the tailnet but not a server (wasapple-tv).
Hostnames are load-bearing: the notes vault's task update runs only when hostname is sextant, and the global Brewfile's (~/.config/homebrew/Brewfile) host case checks for almanac. A rename must update both. Tailscale takes a new name only after it restarts.
The notes vault lives at ~/notes on both Macs—the git clone on sextant, an Obsidian Sync folder on almanac. It syncs through Obsidian Sync to every host, and only sextant runs git on it. On almanac the vault has no .git; never clone, commit, or push it there—its edits reach sextant through Sync, so note history (git log --follow) is only on sextant or GitHub.
The vault's tooling (skills, agents, settings, scripts, memory) is the private repo philoserf/notes-tooling, cloned at ~/.local/share/notes-tooling on both Macs and linked from ~/notes/.claude and ~/notes/.scripts. The vault's CLAUDE.md has the details.
~/.claude is its own repo (philoserf/claude-code-config). On almanac a SessionStart hook pulls it fast-forward-only at every startup and /clear, so commit and push edits made there before the next session, or the pull fails.
Environment
- macOS with zsh as the interactive shell, and Claude Code's Bash tool runs zsh too. Durable scripts—anything committed to a repo—are bash, with a
#!/usr/bin/env bashshebang. Throwaway commands and scripts run in-session in the tool shell execute under zsh, so keep bash-only syntax like associative arrays (declare -A,${!arr[@]}) out of those. - BSD userland, not GNU:
sed -i ''needs the empty backup arg;date/grep/findlack some GNU flags. GNU coreutils are brew-installed with agprefix (gdate,gls, …); GNUsed,grep, andfindare not installed. - In Claude Code's Bash tool,
grepandfindare shell functions running ugrep and bfs, not/usr/bin/grepand/usr/bin/find; scripts run from it still get the BSD tools. They disagree on edge cases (grep -qvon empty input exits 0 under ugrep, 1 under BSD), so don't test a script's grep or find logic by typing it at the tool shell. - The tool shell has
nomatchon andnobareglobqual: an unmatched glob aborts the whole command, and a(N)qualifier is literal. Quote patterns meant for a tool (--include='*.go',-name '*.py'), and usefindfor sweeps that may match nothing. - zsh ties lowercase
path,cdpath,fpath,manpathto their uppercasePATH-style env vars. Never use them as variable names — e.g.while read -r f pathsilently overwrites$PATH, after which every external command fails with "command not found". Usep,fname, etc. instead. for x in $varin zsh iterates once — unquoted expansions do not word-split the way bash's do. Split explicitly:${(f)var}by line,${=var}by word, orwhile IFS= read -r.$(cmd)does split, so the two forms differ.- Quote a word starting with
=(echo "=="), or separate output with---: unquoted, zsh reads it as=cmdexpansion, fails with= not found, and the rest of the command aborts. A PreToolUse hook (hooks/block-equals-word.sh) blocks it in the tool shell; committed scripts are bash and do not expand it. - After a move or delete, check only the new path.
ls old newexits 1 by construction, and a compound command ending in a probe (ls,grep -c,test) reports failure when nothing went wrong. - Formatters rewrite between your edits: a PostToolUse
bun fmthook reformats.md,.mdx, and.markdownon Edit/Write (async, so the reflow can land after your next call), andtask check,task ruff, andtask shellrewrite Python and shell on every run. If an exact-match anchor stops matching, re-read the file; the formatter moved the text. - Stage by name, or
git add -ufor tracked files;git add -Acommits whatever else sits in the tree. A subagent's scratch files go under/private/tmpor the session scratchpad, never the repo, and its brief says so. - A heredoc whose body carries another heredoc ends at the inner terminator: give the outer one a unique word (
PYEOF), or the rest of the body runs as shell. - A
bunxanduvhouse: one-off JS tools run withbunx, Python withuv run(uv run --scriptfor PEP 723) anduvx. A PreToolUse hook (hooks/block-bare-runtimes.sh) rejects barenpx,python, andpipin the tool shell; the same rule holds in plans, docs, and committed scripts, where the hook cannot see. Where a tool's docs saynpx, writebunx; an inherited bare call is a thing to replace, not copy. - A missing tool (Hugo, for one) is a setup step, not a reason to work around it. A repo with its own
Brewfileowns its toolchain: run itstask setuporbrew bundle --file=Brewfilebefore skipping a check or previewing against a live site.
Facts vs. guesses
State only what is observable here. Never attribute a change—a deletion, a move, a missing file—to a device, tool, or sync path; at least five places and many tools change the vault and these repos. Origin is unknown unless a primary record (git log, a journal line, a tool's own output) names it, and then cite the record rather than a conclusion. Label any inference as a guess, in chat and in anything written to a note.
Issues and releases
- Merge pull requests through
~/.claude/bin/pr-cycle merge <pr>—content PRs and dependency sweeps included—not a directgh pr merge; it runs the repo's local gate before merging, in any repo. - I delete milestones after delivery. An empty
state=allmilestone query does not mean milestones were never used — ask before concluding anything from milestone history. - Every open issue in a triage pass gets an explicit disposition (fix / close / defer with reason). List anything parked, explicitly, at the end.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

