Skip to content
Kien

I stopped writing runbooks and started writing slash commands

· 5 min read

~40 commands · 2 companies

Two teams, two companies, ~40 AI slash commands. What changed was not the automation — it was that a runbook and the tool that runs it finally became the same file, and that every dangerous one had to declare itself.

  • AI tooling
  • Claude Code
  • developer experience
  • documentation
On this page
  1. The thing that kept happening
  2. The realisation
  3. What I built
  4. The four decisions that made it work
  5. Two smaller things that mattered more than expected
  6. What I took away

The thing that kept happening

Someone asks in chat: "booking BK-xxxx failed, can you check?"

The answer is a fifteen-step procedure. Find which log source has that identifier — there are four, and which one depends on what kind of id you were handed. Turn the VPN on. Discover the database still times out because a route bound itself to the wrong interface. Fix the route. Query the booking row, then the booking log, then the webhook log. Decide whether the supplier broke or we did.

Everyone on the team could do it. Nobody could do it quickly, because the procedure lived in the head of whoever had done it most recently — and the traps lived nowhere at all. Each new person paid full price for the same half-day.

We had documentation. Nobody opened it. Documentation you have to remember to read loses to a colleague you can just ask.

The realisation

A slash command in Claude Code is a markdown file. The body of the file is the prompt. That is the whole mechanism.

Which means the runbook and the tool are not two artifacts that drift apart — they are one file. Writing the procedure down is shipping the automation. There is no second step where someone converts the wiki page into a script and then the wiki page goes stale.

So the documentation problem became a distribution problem, and distribution I knew how to solve.

What I built

Two packs, one at each company:

  • ~24 commands for the gateway team — reproduce a failure locally, trace any identifier to a timeline, rebuild the exact upstream request from a trace-id, provision or clone a supplier, run a supplier's certification suite, ship.
  • ~18 commands for the other — debug one service end to end, check whether a multi-repo feature actually landed in every repo, reconcile an insurance order against its flight legs, build UI without inventing a second colour system, write the daily worklog from git plus tickets plus chat.

Deliberately two packs and not one. Different infrastructure, different integrations, different traps. They share a shape, not content.

The four decisions that made it work

1. Every command declares its blast radius, in the index.

🟢 read-only   🟠 writes to the production DB   🔴 deploys to production

The risk of handing an agent a shell is not that it does the wrong thing. It is that you cannot tell at a glance which of these forty things is dangerous. Put the marker in the table of contents, not three paragraphs into the body, and the reader is warned before they are curious.

2. Anything that writes follows one protocol, and the protocol lives in the tool.

Dry run → print the plan → get confirmation → apply → re-read and verify.

Written into the conventions is the sentence I care about most:

A command that skips a step is a bug in the command — fix the command rather than working around it by hand.

That single line is what stops the protocol decaying. Once "just this once" is a bug report instead of a shortcut, the guardrail survives contact with a bad day.

3. Never assume where a connection points — make it prove it.

Local config files differ per machine: production, staging, a read replica, a port-forward. So before any write, the command runs SELECT @@hostname, @@read_only and shows the result to the human.

This is not theoretical. On the other side, the trap sitting at number one in the README is: several services commit a local config pointing at production, with the test values commented out. Clone, npm run start:dev, and your laptop is writing to the production database with no warning at all. You identify it by the database name — the test and production names differ by a suffix.

4. Nothing personal is hardcoded.

No /Users/... paths, no personal emails, no user ids, no internal IPs. Everything goes through an environment variable, with a table saying what breaks if you leave it unset. Then a grep gate before commit:

grep -rn "/Users/\|@gmail\|password.*=.*['\"]" .claude/commands/    # must be empty

A default that is silently wrong is worse than making someone type the value in.

Two smaller things that mattered more than expected

Install by symlink, so git pull is the update. Adoption does not die at install, it dies at upgrade. If getting the new version means re-running an installer, the team is running last month's commands within a month.

Set the security bar by who can read the repo, not by whether it is "internal". One of the packs lives in a repository that anyone on the company network can read without logging in and without being added as a member. That is a much wider audience than "private repo", so the bar is higher: no credentials, no real hosts, no ports, no customer data — including in examples. Write down why the bar is where it is, or the next contributor will reasonably assume "internal" means "safe".

And the contribution rule that keeps the packs alive:

Lost more than 30 minutes to a trap? Add it to docs/, with the verbatim error message.

Verbatim, because the next person will not search for your tidy summary. They will paste the exact string the machine printed at them.

What I took away

The best format for a runbook is one the computer can execute. Not because the automation saves the typing — it does, but that is the small win. It is that a document that gets run cannot quietly go stale. It breaks, loudly, the same day the system changes.

Label danger where the eye lands first. A warning buried in the body of a document is a warning for people who already read carefully. The people you need to protect are the ones scanning the index at 11pm.

Put the guardrail in the tool, not in the human. "Remember to dry-run first" is not a safety mechanism, it is a wish. --dry by default, a confirmation gate, and a verification read afterwards — those are mechanisms, and they work when the human is tired.

Write down what you were unsure about. Both packs require a "what I am not certain of" section in their audit output. It costs one paragraph and it is the difference between a document a colleague can build on and a document they have to re-verify from scratch.