DEV Community

Oleh Veheria
Oleh Veheria

Posted on

CLAUDE.md for an iOS Team: What to Put In It (and What to Leave Out)

My first CLAUDE.md for a client project ran 400 lines: architecture diagrams, the full MVVM-vs-TCA debate, a style section that just repeated SwiftLint's config in prose. Claude Code reads that file in full on every single turn, and it still missed rules buried near the bottom, because by line 340 they're competing with everything else for the model's attention. I cut it to 60 lines over two weeks. Same team, same codebase, fewer violations of the rules that actually mattered.

It's not documentation

The instinct is to treat CLAUDE.md like a README: a place to record everything true about the project. A README gets read once by a human who skims for the one section they need. CLAUDE.md gets read in full, by a model, every turn, and every line you add dilutes every other line's share of attention. That's the whole design constraint, and most CLAUDE.md files ignore it.

What earns a line

Non-obvious conventions. Not "we use MVVM," that's visible in five minutes of reading the code. "ViewModels never import UIKit" earns its place only if it's a rule someone actually broke once and it cost a day.

Constraints invisible in the diff. App Store review requirements, a minimum OS version the code doesn't yet reflect, a performance budget on one screen because a past ship got rejected for jank. An agent has no way to infer any of that from the code alone.

Repo-specific workflow gotchas. Which branch triggers a real deploy, which test suite is a known-flaky non-gate versus a hard one, where the actual source of truth lives when two files disagree. I run a merge gate across a few of my own repos, code only merges once CI is green and review found nothing blocking, and the single highest-value line in each CLAUDE.md is the sentence explaining that the gate exists and why a raw git merge is bypassing something on purpose.

What the agent never touches unsupervised. For me that's deploy config and anything security-sensitive. Naming the boundary explicitly is cheaper than discovering it was needed after something already broke.

What doesn't

Anything git blame or the README already answers. Aspirational rules no linter enforces, an unenforced rule just teaches the model that the file's rules are optional. Architecture history and rationale, useful for onboarding a human, irrelevant to the task in front of the agent right now. Anything a linter or type system already blocks mechanically; if SwiftLint already kills force-unwraps, writing "avoid force-unwraps" adds length, not signal.

The before/after: the 400-line version spelled out the Git branching strategy, every third-party dependency and why it was picked, and a style section duplicating the linter. The 60-line version points at the linter instead of repeating it, names the merge gate and why, and states the one ViewModel/UIKit rule an agent (and a couple of humans) had actually gotten wrong before. The short file is the harder one to write, it forces you to decide what actually matters instead of writing down everything you know.

This is the same standardize-the-few-things-that-compound instinct behind modularizing a legacy iOS app without stopping delivery: legislate the few rules that matter across the whole team, leave the rest to local judgment. And it only holds if the codebase backs it up. A CLAUDE.md pointing at "the linter enforces this" is only as good as that linter config being current and actually run in CI.

What's the longest-standing rule in your own CLAUDE.md that you're not sure is still true?

If you're standing up this workflow on an iOS team and want a second pair of eyes on what belongs in the file versus what's just noise, that's the kind of thing I help with.


Originally published at veheria.tech/blog/claude-md-for-ios-teams.

Top comments (0)