Pro practices: hardening the workflow
Five hardening practices (hooks, headless runs, PR review, scoped subagents, config hygiene), each drawn from a real scar in my own usage history.
You will learn
- How to turn prompt-enforced acceptance gates into mechanical hooks, with a formatter on every edit and a test run on stop
- Why true AFK is a headless run picking up labeled issues, not an interactive session kept alive by hand
- Why a PR-per-slice review loop is the habit a senior engineer will trust most
- How least-privilege subagent definitions shrink the blast radius behind my worst incidents
- Why config drift is a real failure mode, and how a periodic /insights retro catches it
The chapters so far are a pipeline. This one is a toolkit: five practices that don't depend on each other, bundled because they share a purpose, which is preventing the expensive failures. None of them came from a best-practices listicle. Each one traces back to a real incident in my own usage history, and I've kept the scars in, because the incident is what tells you which failure the practice is actually for.
Hooks as quality gates#
Right now I run exactly one hook. It's a global PreToolUse guard on Bash called
rm-guard.py, and it exists because of a specific afternoon:
Why this exists: a subagent once ran
rm -rfon an absolute path that resolved above the project root (made worse by macOS case-insensitive paths) and deleted an entire folder. This makes that class of mistake impossible from any agent or subagent whose Bash calls pass through PreToolUse hooks.
There was a second, quieter version of the same lesson. A routine cleanup,
rm -rf .playwright-mcp/, took out 23 tracked screenshots along with it, and I only
got them back because they were tracked and I restored them after the fact. Both
incidents share a shape: a destructive action nobody would have approved, caught late
or not at all.
One guard against catastrophe is not a quality system. The acceptance gates from the
execution chapter, "npm test is green, the deploy
dry-run passes", are enforced by prompt today. I tell the orchestrator to run them
and trust that it does. The upgrade is a PostToolUse hook that lints and formats on
every edit, and a Stop hook that runs the test gate before the session can end.
Then the rules stop depending on anyone remembering them.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npm run lint -- --fix && npm run format" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "npm test" }
]
}
]
}
}Headless runs and CI#
I've described "overnight builds" before, and the description flattered them. In
practice they were interactive sessions I kept alive by hand, threaded across context
resets with SESSION_STATE.md handoffs. A person on watch, nudging a terminal along,
is not AFK.
True unattended work is a headless run. One line, no TUI, no supervision:
claude -p "Implement the slice described in issue #142, then open a PR" --allowedTools "Read,Edit,Bash"The queue for it already exists. The to-issues label state machine moves work
through ready-for-agent, a state that exists precisely so a GitHub Action can watch
for it, pick up a labeled issue, and dispatch a headless run against it. Wire those
two together and the overnight build stops needing a person on watch.
PR-based review#
The numbers here are unflattering, so I'll state them plainly. Over a month of
history I logged 302 commits, and almost all of them merged locally, with barely any
gh pr usage at all. Review, when it happened, happened at the diff in my own
working tree. That's real supervision, but it leaves no durable artifact and no gate
a second person could stand at.
Why it matters showed up in an /insights report: a spawned subagent drifted off its
mission and attempted an unauthorized push and PR merge, and kept trying even after
it had been halted. Nothing catastrophic happened. Still, when the only thing between
an agent and main is that agent's own judgment, "halted" is not the same as
"unable."
The fix is structural rather than a sterner prompt. The orchestrator opens a PR per
slice. Review gates the merge, whether that's a human, , or the
review-checklist skill. And no subagent has push rights at all, so drifting
off-mission can't reach main even when the agent decides it should. Review the
agent the way you'd review a teammate. It turns "I trust my own eyes" into "the
process doesn't depend on my eyes being open."
Custom subagent definitions#
Today there are zero files in .claude/agents/. Every subagent I spawn is a generic
general-purpose agent with the broad default toolset, which means every one of them
could, in principle, run the rm -rf from the first incident or the rogue push from
the third. The blast radius behind those incidents is the default configuration.
The lever is least privilege, expressed as typed agent definitions: .claude/agents/*.md
files with a narrowed tool list. A reviewer that is read-only can't delete or push,
so you can hand it the diff without a second thought. A test-writer that can't touch
src/ can't "fix" the implementation to make a test pass. Scoped definitions convert
"I hope this agent stays in its lane" into "this agent has no tool for leaving it."
They're also what make the headless runs from the previous practice safe to leave
unattended. An overnight claude -p run is only trustworthy when the agent behind it
physically can't push to main or rm -rf a real directory, and a scoped definition
is how you take those tools off the table before you walk away.
Config as truth#
The last one is hygiene, and an /insights audit found more drift than I expected.
Of six configured MCP servers, five showed zero calls in any retained session. Only
Playwright was ever actually used. The project CLAUDE.md still declared the stack
was Vercel long after the site had moved to a Cloudflare static export. The
permission allowlist had accreted 50-plus one-off entries, each added to unblock a
single moment and never removed.
None of that crashes anything, which is what makes it dangerous. Config that says one
thing while the work does another means every agent that reads it starts from a
slightly false picture. The remedy is unglamorous: prune the dead servers, correct
the stale stack notes, consolidate the one-off permissions into rules that mean
something. And run /insights periodically as a retro on your own practice. I know
it catches this class of rot because this entire chapter exists: one run of it
surfaced every scar above.
Where to go from here#
That's the whole course, from a first session to a
real project setup, through
context discipline and
planning into a
multi-agent build, and finally to the practices that
keep it from failing expensively. If you entered partway through via a ?start= link, loop back to the chapters
you skipped, since the workflow only holds together as a chain. And for the depth
this course deliberately compresses, keep the official
Claude Code docs open beside you.
Recap
- Encode your acceptance gates as hooks (a PostToolUse formatter on every edit, a Stop hook running the test suite) so quality is mechanical, not remembered.
- Move overnight work from interactive sessions on life support to headless `claude -p` runs and CI that picks up `ready-for-agent` issues.
- Open a PR per slice and gate the merge on review. Reviewing the agent like a teammate is the habit a senior engineer will trust most.
- Define least-privilege subagents (a read-only reviewer, a test-writer walled off from src/) instead of handing every spawn broad tools.
- Treat config as truth. Prune dead MCP servers, correct stale CLAUDE.md, consolidate one-off permissions, and run /insights periodically as the retro that catches the drift.