Configuring Claude Code for Teams
How to set up CLAUDE.md, permissions, slash commands, skills, hooks, and CI so Claude Code behaves predictably across a team.
Claude Code has a handful of configuration surfaces that, once you understand how they interact, let you run it safely and consistently across a team instead of relying on every engineer remembering the right instructions each session. This article covers CLAUDE.md scopes, permission design in settings.json, when to reach for a slash command versus a skill versus a hook, plan mode, and headless CI/CD usage. Getting the mental model wrong in any one produces either a setup too dangerous to trust with real access, or one so locked down nobody bothers using it agentically at all.
CLAUDE.md and how the memory hierarchy actually merges
CLAUDE.md is a memory file Claude Code loads into context automatically at the start of a session. It's where you put project conventions, build and test commands, architectural constraints, and anything else you want Claude to already know — persistent context, not a one-off instruction typed into a session.
Claude Code looks for CLAUDE.md at four scopes:
- Enterprise: organization-wide policy, managed centrally, applies to every user and project.
- User: personal preferences that follow you across all your projects (for example, "write commit messages in imperative mood").
- Project: checked into the repo, shared with every teammate (build commands, conventions, directory layout).
- Local: project-specific but not committed, for private notes or machine-specific paths.
The detail that trips people up: these scopes are merged, not layered as overrides. A more specific file — say, project-level — does not replace or override what an enterprise-level or user-level CLAUDE.md says. It adds to it. All applicable content ends up concatenated into context together, with no precedence resolution where the closest file wins.
This has a concrete consequence. If your enterprise CLAUDE.md says "never commit directly to main" and your project CLAUDE.md says nothing about branching, that rule is still active in every session inside that project. You can't override an inherited instruction by staying quiet about it at a lower scope, and contradicting it doesn't cleanly override it either — if a project-level file says "direct commits to main are fine here," both statements land in context together, and Claude has to reconcile the contradiction rather than pick the more specific one. If you need an exception, build it into the policy at the scope that owns it. Claude Code does have a defined load order across scopes, but that affects how instructions are sequenced, not which ones are honored — if two files genuinely disagree, that's an authoring problem to fix directly, not something load order resolves for you.
A realistic project-level CLAUDE.md looks something like this:
## Project: billing-service
Build & test:
- Run tests: npm run test
- Lint: npm run lint (must pass before any commit)
Conventions:
- Currency values are stored as integer cents, never floats.
- New API routes go in src/routes/ with a matching contract
test in tests/contracts/.
- Check package.json before adding a new dependency.
Known gotchas:
- Staging DB is shared. Never run destructive migrations
against it without posting in #billing-eng.
- No ORM in this repo. Raw SQL in src/db/queries/ is the
pattern; keep it that way.
Good candidates: exact test and lint commands, naming conventions, directory notes, known gotchas, pointers to other docs. Bad candidates: anything that changes often, secrets, or content so long it crowds out useful context — CLAUDE.md is loaded every session, a real recurring token cost. Keep it current; a stale command from two refactors ago is worse than no CLAUDE.md at all.
settings.json and permission design
Where CLAUDE.md tells Claude what it should know, settings.json tells Claude Code what it's allowed to do — permissions, hooks, and environment variables, at the user or project level.
Permission rules fall into three categories — allow, deny, and ask — targeting a tool or a specific command pattern, not just a whole tool category. Allowing all of Bash is very different from allowing only npm test. When Claude Code is about to use a tool, it checks these rules to decide whether to proceed silently, block outright, or pause for confirmation.
The goal is the narrowest set that still lets the workflow complete — not "deny everything," not "allow everything for convenience":
- Blanket allow (all of Bash, say) removes friction but also removes the safety net — one misinterpreted instruction can run a destructive command with no checkpoint.
- Blanket ask keeps a human technically in the loop but destroys the point of an agentic workflow — you end up rubber-stamping dozens of prompts a session, training yourself to click yes reflexively.
- Narrow, workload-specific rules allow the low-risk, high-frequency operations a workflow needs (tests, reads, formatting), ask for medium-risk operations, and deny what should never happen regardless of context (pushing to main, deleting a database, curl-ing arbitrary hosts).
A permissions block for a team that wants Claude to run tests and linters, read freely, and propose commits, but never push to a remote or silently install packages, looks like this:
{
"permissions": {
"allow": [
"Bash(npm test:*)",
"Bash(npm run lint:*)",
"Read",
"Grep",
"Glob"
],
"ask": [
"Bash(npm install:*)",
"Bash(npm uninstall:*)"
],
"deny": [
"Bash(git push:*)",
"Bash(rm -rf:*)",
"Bash(curl:*)"
]
}
}
Notice git push is denied, not asked — "never push directly" means never, and an ask rule still lets a distracted reviewer approve it out of habit. Package installation is ask because it depends on which package and why.
settings.json can exist at the user level and the project level (checked into the repo, shared with the team). Project-level settings.json standardizes what an agent may do in a codebase without depending on every engineer configuring it themselves. It's also where hooks and environment variables get set — permissions get the most attention, but this file is the general behavior-configuration surface.
Slash commands vs. skills vs. hooks
These three get reached for in overlapping situations, and picking the wrong one produces something that technically works but is annoying to use or unreliable. For slash commands versus skills, the question is who decides when this runs. For hooks, it's different: does this need to happen every time, unconditionally?
Slash commands
A slash command is a reusable prompt template the user invokes explicitly by typing /name — say, a /review-pr command that expands into a prompt checking a diff against your team's style guide. The user is the trigger; Claude never decides on its own to run one. Slash commands fit repeated workflows you always know in advance you want: a release checklist, a changelog draft, a standard review pass.
Skills
A skill is a packaged, discoverable capability with its own instructions and possibly its own resources or scripts, that Claude invokes autonomously when it judges the current task matches the skill's purpose. Claude reads the task, considers the skills available, and decides for itself whether one applies. Skills fit situations where you can't predict in advance exactly when a capability is needed, or where the user shouldn't have to remember a command name — "how to write database migrations in this repo's style" fits a skill better than a slash command, because Claude should reach for it any time a migration task comes up, even described in plain language.
These aren't competing for the same job — a mature setup often has both: a skill that lets Claude autonomously recognize "this looks like a security-sensitive change," plus an explicit /security-review command for forcing that scrutiny on demand. Write for the audience that has to recognize it: a user scanning a command list needs a memorable name; Claude scanning skill descriptions needs a specific description of when it applies, or it'll fire when it shouldn't or get ignored when it should.
Hooks
A hook is a shell command that fires automatically on a lifecycle event — before a tool runs, after a tool runs, when the session stops — configured in settings.json and executed deterministically, with no chance of being skipped because Claude reasoned its way around it.
The distinction that matters: prompted instructions are probabilistic, hooks are deterministic. "Always run the linter before committing" in CLAUDE.md usually works, but it's still natural language interpreted by a model, and under competing instructions or a long context window it can be skipped. If a requirement must happen every time with no exceptions, a hook removes the ambiguity — the harness executes a fixed command rather than the model deciding whether to comply.
Common lifecycle events:
- PreToolUse: fires before a tool call executes — for validating or blocking an action, catching a dangerous pattern permissions didn't.
- PostToolUse: fires after a tool call completes — for automatically running a formatter after a file edit, without depending on Claude remembering.
- Stop: fires when Claude finishes responding — for enforcing "run the full test suite before ending the turn" unconditionally.
A PreToolUse hook that blocks a dangerous Bash pattern before it executes, even one that slipped past your permission rules, might look like this:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "scripts/check-dangerous-command.sh"
}
]
}
]
}
}
check-dangerous-command.sh inspects the proposed command, exits non-zero with a reason if it matches a pattern like a force-push, and exits zero otherwise. Hooks and deny rules can look similar — both can block an action — but hooks can also run arbitrary logic beyond a fixed check: validating file contents, checking external state, injecting context back into the conversation. Permissions are a rulebook keyed on tool and command pattern; hooks are a more general enforcement mechanism for anything that must happen unconditionally at a defined lifecycle point, including logic that has nothing to do with permissions, like reformatting a file after every edit.
Plan mode for risky or hard-to-reverse work
Plan mode is a read-only mode: while active, Claude can research the codebase — read files, search, run non-mutating commands — but cannot make edits or run mutating commands. Instead it produces a proposed plan, which a human reviews and explicitly approves before any change happens, rather than discovering what happened after the fact by reading a diff.
Reach for it on changes that are risky, broad in scope, or hard to reverse: a schema migration touching a production database, a refactor spanning dozens of files, changes to authentication or billing logic, or anything where the best approach is genuinely ambiguous. It's not the default for routine work — using it to fix a typo adds friction without adding safety. The judgment call tracks consequence and reversibility, not diff size.
Plan mode is a different mechanism from permissions and hooks. Permissions gate individual tool calls against static rules; hooks deterministically enforce lifecycle behavior; plan mode front-loads a human review of Claude's overall approach for a whole unit of risky work. It doesn't replace good permissions or hooks — those still apply during execution after a plan is approved. Plan mode answers "is this the right approach," not "is this specific command safe to run."
The read-only constraint is about mutation, not information-gathering: Claude can still read files, search, or query a schema while planning — it just can't edit files, run migrations, or commit. Plans produced this way tend to be better grounded than one proposed without investigation, but the checkpoint only has value if approval is a real evaluation — skimming and reflexively approving gets the added latency with none of the safety benefit.
CI/CD and headless usage
Headless mode runs Claude Code non-interactively, with no human watching to answer prompts. This is what embeds it into CI/CD pipelines: a build step that reviews a diff, generates release notes, fixes a failing test, or performs some other bounded task, triggered by a pull request or scheduled job.
The core fact to design around: in an interactive session, an ask rule pauses and waits for you to answer. In headless mode there's no one there to answer. Permissions must be fully configured before the run starts — an ask rule that would have paused politely in your terminal instead stalls or fails the pipeline step.
Practically, a CI-specific settings.json needs allow and deny rules exhaustive enough that the pipeline's task can complete without hitting an unresolved ask. Fixing a stalling pipeline by allowing everything just reintroduces the blanket-allow problem in a higher-stakes environment — unattended write access to your repo and infrastructure. The right fix is precise upfront scoping: convert exactly the ask rules that workflow needs into allow rules, and leave genuinely dangerous operations denied.
Headless mode fits bounded, well-defined tasks with a clear success condition: automated PR review comments, changelog generation, a fix-and-verify loop against a failing test, or triaging an issue and proposing a labeled response. Open-ended, judgment-heavy tasks are a worse fit, since nothing can redirect Claude mid-task if its approach goes sideways — the checkpoint has to be replaced by tight upfront scoping instead.
CI/CD is also a natural place for subagents — separately configured agents with their own system prompt and restricted tool access, delegated a scoped piece of work by a main agent. A pipeline might use one purely for running and interpreting test output, with no permission to touch deployment configuration, keeping that piece's blast radius small even within an already-headless run.
Common mistakes
- Treating CLAUDE.md scope as an override hierarchy. A project-level rule meant to cancel an enterprise-level one doesn't work — both land in context together and Claude has to reconcile the contradiction.
- Letting CLAUDE.md rot. It's loaded every session, so a stale build command or outdated directory note is actively misleading, not harmless clutter.
- Permission sets that are all-or-nothing. Broad allow categories or near-total deny both miss the point — many small, specific allow rules plus deny for genuinely dangerous operations, with ask for real gray areas, is what works.
- Reaching for a hook when a slash command or skill would do, or vice versa. If a requirement can tolerate an occasional miss, an instruction or skill is enough. If it must happen every time, that belongs in a hook.
- Using plan mode for everything, or for nothing. Forcing it on trivial edits adds latency with no benefit; skipping it on a production migration removes the one checkpoint that catches a bad assumption before it becomes a diff — and approving a plan reflexively delivers none of that safety value anyway.
- Shipping ask rules into a CI pipeline unchanged, or fixing a stalled pipeline by allowing everything. Rules tuned for an interactive session will stall a headless run the first time they fire; allowing everything to unblock it just trades a stall for unattended write access with no human checkpoint. Scope the specific rules the task needs instead.