How we run Claude Code on a 5-developer .NET team — with our actual CLAUDE.md, hooks, and skills
Our real production setup, published: the hook roster, five subagents with model tiers, eight MCP servers, and the machinery that makes agent output shippable.
“How we use X” posts are the most honest genre in developer marketing, because they are checkable — either the configs are real or they aren’t. This one contains our actual setup: the hook roster from our settings.json, the five subagents with their model tiers, the eight MCP servers, and the scheduling machinery underneath — the production configuration a five-developer .NET agency runs client work on, published because most of what made it work was learned from other people publishing theirs.
The context, briefly: we are Code Majesty Tech, five senior .NET engineers, shipping SaaS builds and sprint work for clients from a shared internal template — five connected applications, 23 features, the case study is public. Every developer runs Claude Code daily. What follows is what “runs” actually means.
The stack at a glance
| Component | Count | The short version |
|---|---|---|
| Hooks | 12 across 4 events | Deterministic gates on session start, tool use, and stop |
| MCP servers | 8 | Compiler truth, project context, docs, DB, browser, observability |
| Custom subagents | 5 | Tiered by model — Haiku researches, Sonnet reviews |
| Skills | 10 | Static context packs: patterns, structure, stack, references |
| CLAUDE.md | ~970 lines | The spec — annotated template published separately |
Each piece exists because a specific failure taught us it should. That is the honest organizing principle of the whole setup — nothing below was designed in advance; all of it accreted from incidents.
The hook roster, annotated
Hooks are where advisory stops and enforcement starts. Ours, by event, from the real settings.json:
SessionStart — an MCP health check (a dead server silently degrades every downstream layer, so we verify before work starts), and a post-compact context re-injection that restates the critical project rules after context compaction, because compaction is precisely when agents forget the rules that matter.
PreToolUse — the protective wall, six hooks deep: a git-command guard (no direct commits or pushes — ever; syncing is a dedicated command), a worktree denial (our template’s fork workflow breaks with worktrees), a .claude/-writes guard (the agent does not modify its own guardrails — worth pausing on: the configuration that constrains the agent is write-protected from the agent), the investigation gate that blocks edits referencing unread symbols, and the oscillation detector that blocks edit-thrashing before the reversal lands.
PostToolUse — auto-formatting after edits (arguments about whitespace are a token tax; the formatter ends them), and a context-budget warning that tells the agent when it is burning context faster than progress justifies.
Stop — the verdict layer: a quality gate that runs the build and tests when the agent claims completion (“it compiles” is not done), a convention check against pattern snapshots, and a risk-based review hook that decides — from the shape of the diff — whether this change warrants launching a reviewer subagent.
The pattern worth stealing is the event placement: prevention on PreToolUse, hygiene on PostToolUse, judgment on Stop. Each check lives at the moment it is cheapest.
The CLAUDE.md — three sections beyond the template
The full annotated template is public; three sections of the real file deserve their own mention because they shape daily work the most.
Mandatory recommendations. Our question-style rule requires that whenever the agent asks us anything with discernible options, it must state which option it recommends and why — grounded in code it actually read, not vibes. This sounds cosmetic; it is not. It converts every clarifying question from a delegation of thinking back to the human into a reviewable proposal, and it exposed more shallow investigation than any other single rule — an agent that cannot justify a recommendation has not earned the question.
The automation sentinel. A declared phrase in the message skips all clarification and executes directly. This one line is what lets the same configuration serve interactive sessions (where questions are welcome) and the scripted task runner (where a question hangs the pipeline) without maintaining two rule sets.
Investigation as a named, enforced procedure. The file does not say “understand before acting” — it names the exact tool sequence: query the project-context MCP for the documentation map, read the relevant sections, verify stale claims against the semantic tools, then touch code. And because the investigation gate enforces it, the section is a description of what happens, not a request.
Five subagents, tiered on purpose
From .claude/agents/, with their real model assignments:
| Agent | Model | Job |
|---|---|---|
investigator | Haiku | Read-only codebase research — patterns, architecture, current state. ~90% cheaper than a frontier model for lookup work |
convention-checker | Haiku | Checks staged changes against pattern snapshots — naming, async suffixes, DI registration — reporting violations with file:line |
reviewer | Sonnet | Full quality review against project standards; runs build and tests itself rather than trusting the diff |
design-reviewer | Sonnet | UI changes against the design system |
mobile-design-reviewer | Sonnet | The same, at mobile breakpoints |
This is model tiering as an org chart: Haiku agents do the reading, Sonnet agents do the judging, and the top tier is reserved for the main session’s planning. The economic effect is that research — the highest-volume activity in any real session — runs at the cheapest tier, and the review capacity effectively became free enough that everything gets reviewed, which was never true when review required a human calendar.
The eight MCP servers
The roster: roslyn — our own C# server exposing compiler-grade symbol tools; the single highest-leverage piece. project-context — the second custom server, and the quiet workhorse: it serves the documentation map for investigations, maintains the AUTO-TABLE inventories, runs hybrid BM25 + embedding search, and hosts the build-outcome confidence data. serena — general semantic code navigation, complementing Roslyn on non-C# files. context7 — current library documentation, because training-data API knowledge ages badly. postgres — schema-aware database access in development. playwright — browser automation for visual checks. grafana — observability queries, disabled by default and enabled when debugging needs it. mobile-mcp — device testing for the mobile review agent.
The two custom servers are where the twelve-layer system physically lives; the other six are assembled from the ecosystem. That ratio — two built, six adopted — is probably the right expectation to set for anyone budgeting this.
The ten skills
Skills are the static-context half of the system — curated packs the agent loads on demand instead of re-deriving: project-patterns (implementation patterns with examples), project-structure (the directory map with intent annotations), tech-context (the stack, pinned versions, and the why behind each choice), project-reference and reading-list (where to look things up, in what order), explore-templates (how to navigate the template’s fork structure), plus domain packs for email (resend, email-best-practices), diagrams (mermaid), and Postgres practices. The dividing rule between a skill and a CLAUDE.md section: rules the agent must always hold live in CLAUDE.md; context it sometimes needs lives in a skill. Getting that split wrong in either direction is a context-budget leak — always-loaded reference material starves the session, and rules demoted to skills stop being rules.
A week on this setup, concretely
Monday is planning-heavy: feature specs get written and run through planning passes at the top model tier, producing task files with evidence annotations and effort tiers. Tuesday through Thursday the task runner burns through them — resumed sessions, investigation-gated, build-gated, with the humans doing kickoffs, reviews of agent PRs, and the client calls agents cannot do. A developer typically has one runner going and one interactive session for the gnarly work; the Stop-hook review layer means agent PRs arrive pre-checked, so human review is judgment, not linting. Friday skews human: retro on the outcome logs (what patterns failed, what the confidence scores moved on), hook and snapshot maintenance from the week’s incidents, and doc-sync runs in fresh sessions. The rhythm that emerged — nobody designed it — is that agents compress the middle of the week and the humans reclaimed its edges: Monday for thinking ahead, Friday for thinking back.
The seniority question
The composition of this team is five seniors and zero juniors, and honesty requires saying the system depends on that. Every layer above assumes someone who can judge agent output is in the loop — the reviews, the recommendation-mandated questions, the same-day hook fixes all presume engineering judgment that juniors are still building. We genuinely do not know how this setup lands in a team of two seniors and six juniors; our suspicion is that the guardrails help (the investigation gate teaches the discipline it enforces, and watching an agent get blocked is a lesson), but that unsupervised agent output reviewed by junior engineers is the exact configuration where plausible-but-wrong code ships. If you run that experiment, publish the results — it is the biggest open question in this whole space, and the teams that answer it well will define how the next generation of engineers gets trained.
The machinery nobody blogs about
Two pieces of scheduling infrastructure matter more than any individual hook, and we have never seen either written up elsewhere.
Session continuity. Our task runner keeps all tasks within a feature in a single resumed Claude session — it saves the session ID after the first task and passes --resume for the rest, falling back to fresh sessions only when resume fails. The reason is prompt-cache economics: the CLAUDE.md, tool definitions, and accumulated project context are already cached from the previous task, so subsequent tasks start warm. Doc-sync runs deliberately get fresh sessions instead — clean context for a different kind of work. Cache-aware session management is an unglamorous line item that meaningfully changes the monthly bill.
Adaptive thinking effort. Task files can declare a reasoning-effort tier; when they don’t, the runner derives one from the task type — and then consults the build-outcome history before committing. From the real spec: a ≥90% success rate at the current effort across five-plus samples downgrades effort one tier; a >20% retry rate upgrades it; explicit declarations are never overridden, and every override is logged. Which means the Bayesian outcome data doesn’t just gate plans — it auto-tunes how hard the model thinks, per task type, from evidence. The system learns where thinking is wasted and stops paying for it there.
What it costs and what breaks
The honest section. Maintenance is real: the hooks are ~a dozen scripts that rot like any code — conventions change, a scanner misses a new pattern, and the fix discipline (same-day, or the layer silently decays) is a genuine tax we pay. MSBuild workspaces have moods: the Roslyn server occasionally needs a restart, and we designed it stateless for exactly that reason. The token bill is real money — tiering and session continuity keep it proportional to output rather than to enthusiasm, but a five-person team running agents daily is a real line item, and anyone who says otherwise is not running them daily. And the discipline came before the tooling: every layer here mechanizes a practice we already believed in. Teams hoping the hooks will install engineering discipline have the causality backwards — the system enforces values; it cannot supply them.
What we got for all that: the internal template — 23 features, five applications, six weeks, five people — and client sprint work whose pricing model only works because the system holds.
Steal it in this order
Everything above is reproducible from this blog — and the starter versions are clonable: dotnet-claude-template ships the CLAUDE.md, the investigation gate, oscillation detector, build gate, and two tiered agents; dotnet-mcp-starter is the minimal Roslyn MCP server. The adoption order: CLAUDE.md first (an afternoon), then the gates, then the oscillation detector, then the Roslyn server when your solution outgrows grep. The full twelve-layer map and the field guide cover the rest of the path, including when not to bother.
Or compress the path: a one-hour Claude-Code-for-.NET review is $80 — your setup against this one, gap list included — and the two-week Sprint Zero installs the whole stack against your repository with your team watching. Either way, the configs in this post are real, and the reason we publish them is the reason you should trust them: this is checkable.
Related Posts
AI coding guardrails for regulated industries: what we ship for fintech, govtech and assurance clients
How the twelve-layer system maps to GDPR, DORA, NIS2, and SOC2 — with the audit artifacts each layer produces as a side effect of normal development.
AUTO-TABLE inventories: a structured codebase index Claude Code can actually trust
Deterministic, auto-regenerated codebase inventories embedded in your docs — the structured index that lets Claude Code stop re-deriving your architecture.
Bayesian build-outcome confidence: telling Claude when its plan is likely to fail before it costs you a build
Your agent already generates the data to predict its own build failures. How we score plan steps with Bayesian pattern confidence and cascade-risk analysis.