[FEATURE] Role-scoped configuration: fully independent CLAUDE.md, skills, agents, hooks and permissions per role in one repo

Status Open
Maintainer reply None cached
Activity 0 comments · opened Jul 27, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Our repository is worked on by a mixed team: developers, QA engineers, DevOps, and a business analyst who maintains requirements and specs in the same repo. Everyone runs Claude Code against the same clone, and Claude Code has no notion of who is running it.

The failure mode is concrete. Our BA opens Claude Code to update a specification document. She asks something like "check whether the current implementation matches this spec." Claude, holding the shared project CLAUDE.md that tells it how to fix things, starts editing source files. Nothing in the configuration says that this person's job is to read code and write documents, not to change code. The result is unreviewed edits from someone who isn't a developer, on files she never intended to touch.

The same shape recurs with QA: a QA engineer asks Claude to reproduce a bug, and Claude — reading developer-oriented project memory — proposes and runs a database migration against the staging environment.
What I tried. I attempted to solve this with skills: a skill that fires at session start, asks "who are you working as?", and then instructs Claude to load the right context and stay within the right boundaries. This does not work reliably:

Skill invocation is model-driven, so the question is asked sometimes and skipped others depending on how the session opens.

Once the role is established, it decays. Twenty turns in — especially after /compact — Claude no longer treats "you are the BA, do not modify source" as binding.

It is instruction, not enforcement. A prompt telling Claude not to edit files is a suggestion the model can talk itself out of; the Edit and Write tools remain fully available the entire time.

It has no effect on what is loaded. Every role still pays the context cost of every skill and the full shared CLAUDE.md, because loading has already happened by the time the question is asked.

The general problem: Claude Code resolves configuration along two axes — user (~/.claude/) and project (.claude/) — and has no axis for what this person does on this project. So the one thing most worth sharing with the team, the role setup, is the only part that cannot be committed alongside the code. Teams are left choosing between one union config that gives everyone everything, or per-person local config that isn't shared, isn't reviewed, and drifts within days.

What a role needs to control — and specifically, this is not just a skills question:

Writable and read-only directories. This is the piece with no workaround at all today. A role should declare its editable scope: the BA may write to docs/ and specs/ and read everything else; QA may write to tests/ and fixtures/; the developer may write to src/ but not to infra/. Enforced at the tool layer, so an out-of-scope edit is blocked rather than discouraged. Path-level permission rules exist for Bash commands, but there is no per-role notion of "these directories are yours, the rest is read-only."

CLAUDE.md — the BA needs the domain and requirements context; the developer needs architecture and conventions. Merging both into one file means everyone gets instructions written for someone else, and those instructions actively misdirect the model, as in the case above.

Skills, subagents, MCP servers — loaded per role rather than all at once.
Command permissions — migrations, deploys, force-push denied for roles that have no business running them.
Model and effort defaults — differ meaningfully between spec review and implementation work.

Crucially, this has to be enforcement rather than instruction, and it has to be declarative configuration committed to the repository — not something the model asks about at session start and gradually forgets.

Proposed Solution

Introduce roles as a first-class configuration layer that is committed to the repository and sits between the project layer and personal local settings. A role is a complete, independent configuration — not a filter over the project one — and its boundaries are enforced by the tool layer rather than suggested by prompt.

  1. Role directories mirror the existing .claude/ layout

.claude/
CLAUDE.md # shared: repo layout, conventions, what not to touch
settings.json # shared baseline
skills/ # skills everyone needs
roles/
dev/
CLAUDE.md
settings.json
skills/
agents/
hooks/
qa/
CLAUDE.md
settings.json
skills/
agents/
ba/
CLAUDE.md
settings.json
skills/
Everything Claude Code already reads from .claude/ can also live under .claude/roles/<role>/. Nothing new to learn: same file names, same formats, one extra directory level.

  1. Writable scope is declared per role and enforced by the tools

.claude/roles/ba/settings.json:
json{
"workspace": {
"write": ["docs/", "specs/", "adr/"],
"read": ["
"]
},
"permissions": {
"deny": [
"Bash(python manage.py migrate:*)",
"Bash(./deploy:*)",
"Bash(git push:*)"
]
},
"model": "sonnet"
}
Behaviour: Edit, Write, NotebookEdit and file-writing Bash commands refuse any path outside workspace.write. The refusal names the active role and the rule that blocked it, e.g.:
Blocked: src/api/views.py is outside the writable scope of role "ba"
(writable: docs/, specs/, adr/). Switch roles with /role, or ask a developer.
Claude receives that refusal as a tool result and can reason about it — propose a patch as text, open an issue, hand off — rather than silently continuing as if the edit succeeded.
workspace.read defaults to
. Setting it narrower makes the rest of the tree invisible to Read/Grep/Glob, which is useful for external contributors but is not the primary case.
This is the part with no workaround today. Bash permission rules can gate commands by pattern, but there is no way to say "this person edits documents, not source." That distinction is what a role is for.

  1. Optional role tags for items that stay in the shared folders

Not every skill deserves its own copy under a role directory. A skill can declare its own audience:
yaml---
name: e2e-test-authoring
description: ...
roles: [qa, dev]
---
No roles: field means "all roles". This matters at scale: with a tag, adding a skill requires no edit to any central allow-list — the skill states who needs it and the list maintains itself. The same field would be useful on subagents.

  1. Role selection

In precedence order, highest first:
MechanismUseclaude --role qaCI, wrapper scripts, one-off sessions/role qa in sessionswitching hats mid-day; bare /role lists available roles and shows the active one"role": "ba" in .claude/settings.local.jsonthe normal case — set once per person, gitignoredCLAUDE_ROLE env varcontainers, devcontainers, shared machines
The role definitions are shared and reviewed in PRs; the choice of role is personal and never committed. If no role is resolved and the project defines any, Claude Code prompts once at session start and offers to persist the answer to settings.local.json — deterministically, at startup, not as a model-invoked skill.
A project may set "requireRole": true to refuse to start without one, and "defaultRole" for repos where an unprivileged default is the safe fallback.

  1. Merge semantics (should be explicit in the docs — a four-layer merge is only usable if the rules are stated)

ItemRuleCLAUDE.mdproject file first, role file appended after itworkspace.writerole may only narrow the project set, never widen itpermissions.allowrole may narrow, never widenpermissions.denyunion of project and role; a role can never un-denyskills, agents, MCP serversunion of project and role, filtered by any roles: taghooksrole hooks run in addition to project hooks, unless the role sets "hooks": { "replace": true }model, effort, output stylerole overrides project
Personal settings.local.json may narrow further, but never widen past what the role allows. The invariant across the whole table: each layer can only tighten.

  1. Observability

The active role is visible in the status line, not buried in a config file.
/config (or claude config show) shows which layer each effective setting came from — shared → role:ba → local. Without provenance, debugging a merged config becomes guesswork.
claude config check --role ba validates a role definition in CI, so a broken role is caught in the PR that introduces it.

  1. Backward compatibility

A repository with no .claude/roles/ directory behaves exactly as it does today. No existing field changes meaning. roles: in skill frontmatter is optional and ignored when absent. Roles are additive in every direction.

  1. Explicit non-goal: this is a guardrail, not a security boundary

Role scoping constrains what Claude Code does on someone's behalf. It does not constrain the human, who can edit any file with any editor, and it is not a substitute for repository permissions, branch protection or code review. The value is preventing well-intentioned accidents — the BA whose session started rewriting application code because the shared project memory was written for developers — not defending against a hostile user. Framing it this way keeps the feature honest and keeps its implementation simple.

  1. Minimal viable version

If the full layer is too large to take on at once, the smallest genuinely useful slice is:

.claude/roles/<name>/CLAUDE.md and .claude/roles/<name>/skills/
workspace.write enforcement on file-editing tools
--role flag and settings.local.json selection

That alone fixes the failure described in the problem statement. Hooks, MCP scoping, subagent tags and config provenance can follow.

Alternative Solutions

Skill allow/deny lists (#37463, #43928, #39749): cover only skills, and require maintaining a list of names that goes stale every time someone adds a skill. They don't touch CLAUDE.md, hooks, MCP or permissions.
Persona Profiles (#53458): closest existing request, but framed as a personal mode switch (writing style, model, skills) for one user across domains. It lives in ~/.claude/profiles/ and isn't committed to the repo, so a team can't ship it to new members or review it in a PR.
Subagents (.claude/agents/*.md): they already carry their own tools and model, but they describe roles inside one session, not the human running it. Skills also can't currently be scoped to a specific subagent.
Separate CLAUDE_CONFIG_DIR per role: works mechanically, but discards the project layer entirely and can't be committed, so it solves the personal case and none of the team case.
Separate repos or worktrees per role: absurd for a single codebase, and QA and developers need to look at the same files anyway.

Priority

Medium - Would be very helpful

Feature Category

Configuration and settings

Use Case Example

Setting: a team of nine on a single Django + React monorepo — five developers, two QA engineers, one DevOps, one technical writer. Everyone runs Claude Code on the same clone.
Today
.claude/ contains the union of everything anyone needs: a 400-line CLAUDE.md covering architecture, test strategy, deployment and docs style, plus 23 skills. Every session, for every person, loads all of it.
Concretely, what goes wrong:

A QA engineer asks Claude to reproduce a bug. Claude has the db-migration and deploy-staging skills loaded, and the shared CLAUDE.md says "always run migrations before testing against a fresh DB" — advice written for developers. Claude proposes a migration. The QA engineer isn't sure whether that's expected and approves it. Staging is now in a state nobody asked for.
A developer asks for a fix. The test-matrix skill and 60 lines of QA-specific CLAUDE.md are in context, unused, every single session.
The tech writer wants documentation help and gets a Claude that has been told, by shared project memory, to prefer terse commit-message-style prose.
A new QA hire joins. Onboarding is a Notion page titled "how to configure Claude for QA work on this repo" — six manual steps that half the team has silently diverged from.

The team's workaround is per-person ~/.claude/ setups. Those aren't in the repo, aren't reviewed, and are already inconsistent between the two QA engineers.
With role-scoped configuration
.claude/
CLAUDE.md # 80 lines: repo layout, conventions, what not to touch
settings.json
skills/ # 6 skills everyone genuinely needs
roles/
dev/
CLAUDE.md # architecture, migration policy, review checklist
skills/ # db-migration, api-scaffold, perf-profiling
settings.json
qa/
CLAUDE.md # test matrix, fixtures, staging URLs, bug report format
skills/ # e2e-test-authoring, flake-triage, repro-minimizer
agents/ # regression-runner subagent
settings.json # deny: migrations, deploys, force-push
devops/
docs/
.claude/roles/qa/settings.json:
json{
"permissions": {
"deny": [
"Bash(python manage.py migrate:*)",
"Bash(./deploy:*)",
"Bash(git push --force:*)"
]
},
"mcpServers": ["testrail", "playwright"]
}
The new QA hire clones the repo and runs:
claude --role qa
They immediately get the team's QA context, the QA skills, the regression subagent, the test-management MCP servers — and a Claude that cannot run a migration even if asked. Zero setup steps. The onboarding page is deleted, because the configuration is the documentation, and it is reviewed in PRs like the rest of the code.
A developer picking up a QA task for the afternoon switches in-session:
/role qa
and switches back when done — without editing any file, and without their choice affecting anyone else (the active role lives in gitignored settings.local.json).
Why the outcome is different, not just tidier
The permission boundary is the part that can't be reproduced by any current workaround. Skill allow-lists change what Claude knows about; a role changes what Claude may do. On a shared repo those are different guarantees, and only the second one prevents the staging incident above.

Additional scenarios the same mechanism covers:
CI. claude --role ci runs with a minimal skill set, no interactive-only skills, read-only permissions on everything outside the build directory. Today this requires a hand-maintained parallel config that drifts from the real one.
Contractors and open-source contributors. An external contributor runs --role external: project conventions and public skills, but no internal MCP servers, no infrastructure context, no deploy permissions. The role definition is public in the repo; nothing sensitive leaks through it.
One person, two hats. A solo maintainer switches between --role feature-work and --role release, where the release role loads changelog and versioning skills and denies anything that edits source files. This is the case #53458 describes — role-scoped config covers it too, but as a repo-committed artifact rather than a personal profile, so it survives a machine change and can be handed to whoever takes over the release next month.

Additional Context

Related, but each covers a subset: #53458 (personal profiles, not committed), #37463 and #43928 (skill toggles only), #39749 (skill activation), #20665 (--skills CLI flag), #39403 (shared skill directories).

View original on GitHub ↗