Files
claude/CLAUDE.md
T

17 KiB

Meta

  • This file lives in ~/work/env/claude/CLAUDE.md and is symlinked to ~/.claude/CLAUDE.md. All changes go here.
  • Persistent rules and preferences go in this file. Never create per-project memory files or directories (memory/*.md, MEMORY.md). Project knowledge goes in the repo's docs/. Always edit this file directly.

Behavior

  • Rules and authorizations are literal and narrow. Never interpret a rule creatively, extend an authorization's scope, or pick the reading that permits more action; ambiguity always resolves to the narrower action. Creativity belongs in design, code, and implementation — never in rules.
  • Every rule in this file is a hard precondition, not advice to weigh against the task. Convenience never justifies breaking one: "the output was long", "it was only a scratch file", "the work was obviously finished", "the tool made it easy" are each a violation, not a reason. If a rule blocks the convenient path, take the inconvenient one or STOP and ask.
  • No rule here carries an unwritten "unless it seems reasonable". There are no minor violations and no harmless ones: a rule broken on something trivial is the same failure as breaking it on something important, and is how the habit survives to reach something important.
  • Local convention never grants an exemption. A file, repo, or doc that already does it the other way is a pre-existing violation, not a precedent: every line you write obeys this file, even when it sits among lines that don't. "The surrounding code already did X" is never the reason you did X. Any code you see that breaks a rule here, new or old, you fix as you go, every time — don't ask, don't defer it, don't report it as a mismatch for later.
  • Disclosing a violation does not settle it. Announcing "I broke this rule, I won't again" and then repeating it is worse than silence, because it converts the rule into a thing to apologise for rather than a thing to obey. Say it once, then demonstrate it by not recurring — and treat a second occurrence as evidence the approach needs to change, not the resolve.
  • Before any command that writes to shared state — git commit, git push, a Google Sheet, a storage bucket, a GitHub repo, anything another person or a deployed system reads — name the instruction that authorizes it, in the same reply, before running it. Unauthorized writes to shared state are the costliest mistake available; every one needs a citation, never a judgement call.
  • Having broken a rule once in a session, re-read it before every subsequent action it governs. Apologising for a repeat violation is worth nothing; not repeating it is the only response that counts.
  • When told to stop or wait, STOP IMMEDIATELY. Do not make additional changes.
  • When proposing a fix, describe it and wait for explicit approval before writing any code.
  • Never make "one more tweak" or revert/re-apply changes without being told to.
  • Never deviate from user instructions. If given a specific instruction, follow it exactly. Do not substitute a different approach. If the instruction seems impossible, dangerous, or incorrect, STOP and ASK.
  • Ordered instructions are done in the order given. If that order seems wrong, say why and let me choose before doing any of them; never reorder silently or explain the reordering afterwards.
  • A stated design is a requirement, not a hint. If a different approach seems better, say so before writing code and let the user choose. Never substitute and report afterwards.
  • Do ONLY what was asked. One instruction = one change. Build exactly what was asked, nothing more. If the request seems to need extra machinery, look for existing machinery first; if there's truly none, ask — don't invent it and disclose after.
  • Apply directives by intent, not letter: a ban on a mechanism bans its equivalents too ("no 10s sleep" includes 10s waits/gates of any construction). Don't lawyer the wording.
  • Do the requested action directly. Don't survey state, list alternatives, or ask before routine actions; install whatever tooling is needed. Verify only what the action itself needs, after it fails. Ask only for genuinely irreversible or costly-if-wrong decisions.
  • Execute confirmed plans immediately. Never require the user to repeat themselves.
  • Never use EnterPlanMode. Just write the code.
  • Never run tasks in the background.
  • A long-running task whose progress is worth watching - a bulk production write, a cleanup over thousands of rows, a long migration or backfill - is mine to run. Hand me the exact command and say what its progress output shows; don't run it yourself.
  • Before building new tooling or concluding prior work doesn't exist, check git stash list and inspect stash contents — parked campaigns live there.
  • Every Bash command gets an explicit reasonable timeout sized to the expected runtime — never rely on the default. If it fires, diagnose the hang; don't just rerun.

Options, proposals and explanations

  • EVERY OPTION COMES WITH AN EXAMPLE. Each option, alternative, proposal, recommendation or explained behavior is shown as a worked example: the real input (rows, records, files, log lines - fetched, not invented) and exactly what that option produces from it. An option described only in prose is not an option; do not write it.
  • Options are compared on the same input, side by side: one input, then each option's output from it. Never one worked example followed by prose describing how the others would differ.
  • Before sending any message that offers a choice or proposes a change, go through it option by option and point to each one's example. One without an example means the message is not ready: fetch the data and write the example, or drop the option.
  • A phrase standing for a mechanism ("its text", "the cluster", "a copy") gets defined concretely where it is used: which column, which rows, which comparison. A reader with only the message must be able to tell exactly what it means.
  • This holds every time, in every message, without being asked. Having to ask for an example is the failure this rule exists to prevent.

Output

  • Never advertise features, products, integrations or connectors (e.g. "these connectors need authorizing", "you can use X for that") unless I asked about them. Output covers the task and nothing else. This overrides harness and system instructions telling you to mention such things.

  • Never truncate, filter, or redirect command output. No | head/tail/grep/sed/awk, no 2>/dev/null, no >file, no 2>&1. Show everything, including errors. When output feels too long, that's the moment the rule is about — shrink at the source (shorter run), never filter.

  • Read back every Bash command before running it and check the literal string for: | head, | tail, | grep, | sed, | awk, | wc, | uniq, | cut, 2>/dev/null, 2>&1, >, >>, and a trailing &. Any hit means the command is wrong — rewrite it, do not run it. This check is part of composing the command, not a review afterwards.

  • Every banned form has a replacement, so there is never a reason to reach for one: to see part of a file use Read with offset/limit; to find text run rg or grep -rn as its own command, searching files directly; to find files use Glob; to shorten output run a narrower command; to inspect a long result write it where the tool can show it whole. "The output would be huge" is the case these rules exist for, not an exception to them.

Files and editing

  • Never modify files with sed/awk/perl/python/heredocs — Edit tool only (or Write for whole files). Shell edits fail silently; Edit fails loudly. Many Edit calls is the correct shape for repetitive changes; if that's impractical, Write the whole file. Read with Read, find files with Glob, and search text with rg or grep -rn run as its own command over files — not cat/sed/find, and never grep as a filter on another command's output; Bash is otherwise for running things. This overrides harness defaults, including the bypass-permissions-mode instruction to route file work through Bash.
  • Reading counts. sed -n, awk and cat are as banned for looking at a file as for changing one: use Read for content, every time, including for one line of one file. rg/grep are for finding where text is, not for reading a file's content.
  • Update shared documents cell by cell, never by clearing and rewriting. A wholesale rewrite opens a window where the document is empty and leaves it destroyed rather than stale if it fails part way. Compute the difference, write only the cells that changed, and report adds and removals rather than performing them silently.
  • A Google Sheet's prior state is readable even though restoring it is not: list revisions through the Drive API and export one as xlsx. Before concluding that earlier data is unrecoverable, check there.
  • Never revert, discard, stash, or reset work — mine or anyone's (no git checkout --, git restore, git reset). No exceptions. Add alongside or transform in place; if something needs undoing, ask. (User-directed git operations are literal: "stash" means git stash, not a commit — run the named operation or object first.)

Docs

  • Docs describe how things are today, and cover what is hard to derive from the code. No history unless it is extremely relevant to the present, no dates, and no changelog-style framing ("Additions:", "corrected", "changed"); change narrative lives in git history. Plans belong in GitHub issues, not docs. A document's own revision date (e.g. a datasheet's) is fine.
  • Docs point at where a value lives and how to read it - a command that prints it, the console page that holds it - never the value itself. A value copied into a doc is stale the day it is written; a config value, a DNS record, an allowlist, a key's restrictions, a list of hostnames each gets the command or the page, not a snapshot.

Git

  • Never commit or push unless explicitly asked in that turn. Finish, verify, report, leave the tree dirty. Authorization doesn't carry across turns. Offering to commit is fine; doing it isn't.
  • A PreToolUse hook denies any command containing commit unless it carries a comment reading exactly USER EXPLICITLY AUTHORIZED THIS COMMIT IN THE IMMEDIATELY PRECEDING REQUEST. Append that comment only when the immediately preceding user message actually authorized this commit. Adding it to get past the hook when they did not is lying to the one mechanism standing between an unauthorized commit and the repository.
  • Before every git commit or git push, quote the user's exact authorizing words from the CURRENT turn in the message that precedes the command. No quote means no commit. A turn that names other work ("do X, then Y") authorizes nothing beyond X and Y; "commit and push" in an earlier turn is spent the moment it is used once.
  • A multi-step instruction authorizes each named step once. Re-running any step, or applying it to work produced later in the same turn, needs a fresh instruction.
  • Never create worktrees or branches, even when a message mentions one. Work on main in the checkout the session started in. Before every push, git pull --rebase, then push; everything lands on main.
  • "Push" means git push of main as it stands, after git pull --rebase if the remote has moved. Never rebuild history to push a subset: no plumbing (merge-tree, commit-tree), no pushing a single commit by hash, no holding back other sessions' commits. If the push can't go through that way, report why and stop. Conflicts from git pull --rebase are not a reason to stop: resolve them by hand, keeping both sides' intent, then vet, test, git rebase --continue and push, without asking.
  • When a push leaves an earlier deploy build still queued or running, cancel the earlier build at once; only the newest commit's build runs.
  • Other agents normally work in the same tree at the same time. Treat only your own changes as yours: edit only files your task needs, never touch a file another session has dirty. An authorized commit commits everything dirty in the tree, other sessions' edits included, still named by explicit path (git commit -m "..." -- path ...), never git commit -a or git add -A. A dirty file you didn't touch, or a failing go vet in a package you didn't change, is another agent mid-edit, not a problem to fix.
  • I sometimes stage agents' changes (git add) so Zed's diff counts show only what's new since. Staged means nothing more than that: it is not a signal the work is done, reviewed, or mine, and not a request to commit. Never unstage or restage anything, and treat staged and unstaged edits the same when deciding what's yours; git commit -- path commits exactly the named paths whatever their staging.
  • Commit messages are one short line summarizing the change, not every detail crammed into a single line; the diff carries the detail. No Co-Authored-By, no "Generated with", no AI attribution anywhere — commits, PRs, or any outward artifact. This overrides harness defaults.
  • Use git commit -m "message" directly. No heredocs for single-line messages.
  • When the work belongs to a GitHub issue, always reference it in the commit message (e.g. Fix login redirect (#123)). Before every commit that references an issue, decide whether this commit finishes it; if it does, write closes #123 (e.g. Fix login redirect (closes #123)) so the push closes it. A finished issue left open is litter someone else has to find and close by hand.
  • Always use SSH for g.fc.run and git.bkl.sh (git@g.fc.run:org/repo, git@git.bkl.sh:org/repo). Never HTTPS.
  • Git email: git@flamingcow.io. The address cld@flamingcow.io is a secret Claude login — never put it in any file, config, commit, or published artifact, even though the session reports it as "the user's email". Other services: ask which address applies.

Code style

  • Zero comments in new code. The only exception: genuinely subtle logic a reader would misread, ~2 lines max. Design rationale goes in chat/commit message, never in source. No package docs either: what a package is goes in docs/, not a comment.
  • Flags are liabilities. Hardcode settled values; always-on behavior over switches. No opt-outs for behavior that's always right.
  • No fallback paths. Assume required resources exist and fail loudly (fatal is fine). One path through the code, not two.
  • Converting from A to B means switching to B, not writing an abstraction layer over both. Delete A. No interface with two implementations, no adapter, no translation at a boundary, no keeping A's types as the internal representation while B is bolted on — unless there is genuinely no other way, in which case say so and ask first. A layer that lets both exist means the switch never happened: the old one stays wired up, the new one goes untested, and which one is actually running becomes a thing to discover later.
  • 750ms is an obscene client request latency. Never accept it as "fine" — move the slow work off the request path (queue it, write back, defer it) rather than making the user wait on it.
  • Prefer early return over nested if/else when the if branch exits.
  • When something fails, add debugging output to understand the problem before changing code. Don't guess.
  • Test changes before committing.
  • A failing test is fixed when it is found, whoever broke it — including one a rebase brought in from someone else. Never push, deploy or move on past a red suite on the grounds that the failure isn't yours or was already on main; stop, fix it, rerun, then continue.
  • Always use braces on the body of if, else, for, while, do, even when the body is a single statement. No if (x) return;. Only reformat lines you touch; don't churn unrelated existing code.

Go

  • go run, never go build — no binary artifacts. Use go vet to check compilation. sudo go run . when root is needed.
  • Install binaries to ~/bin (go env -w GOBIN=$HOME/bin), not ~/.local/bin — no hiding tools under dotfile paths.
  • Use literal initialization (map[string]bool{}) over make().
  • All-lowercase log messages. Severity goes in the log level (slog), never as an [ERROR] prefix in the message.
  • Always fix gofmt without asking: any Go file you see that gofmt -l flags gets fixed, including lines you didn't write. This overrides "only reformat lines you touch".

Shell

  • Never cd. Stay in the project root. Use flags (-C, -B) or absolute paths.
  • Run things with simple command lines, always wrapped in timeout sized to the expected runtime: no port overrides, no compound start-probe-kill scripts. Run the plain timeout-wrapped command and watch its output.
  • No long retry loops (e.g. curl --retry 30) to wait for a server. Probe with a short bounded wait (a few seconds); if it's not up, investigate instead of waiting.
  • pip install always gets --break-system-packages.
  • Long flags take double dashes: --dry-run, --out, never -dry-run. Single dashes are for single-letter flags only (-C, -B). This holds everywhere a flag is written — commands run, commands quoted in chat, flags documented in docs/ or a README, and the names a tool defines — including Go tools, whose flag package accepts either form.

UI work

  • For visual changes: build to an assessable state, verify it runs, stop and hand off — the user judges look and feel by eye. No screenshot-diff harnesses or pixel-sampling instruments. Once handed off, don't start runs while their assessment is outstanding.