Settings Architecture

Agentic coding tools separate what the agent knows (instruction files, context) from what the agent is allowed to do (settings, permissions, hooks). Settings govern the latter. Every major tool converges on a layered scope model that lets organizations enforce policy, teams share conventions, and individuals customize their environment — all without polluting the agent’s context window.


Four-Tier Scope

Settings resolve through four layers. Each layer has a distinct owner and sharing model.

ScopeShared?PurposeClaude CodeOpenAI CodexGemini CLI
Managed (Org)Admin-controlledEnterprise policies, complianceManaged settings (pushed by admin)requirements.tomlOrg policies
User (Global)NoPersonal defaults across all projects~/.claude/settings.json~/.codex/config.toml~/.gemini/settings.json
Project (Team)Yes (committed to git)Team-agreed rules and permissions.claude/settings.json.codex/config.tomlProject config
Local (Personal)No (gitignored)Machine-specific overrides, secrets.claude/settings.local.jsonLocal overridesLocal config

The key distinction: Project settings are version-controlled and shared with every team member. Local settings are gitignored and never leave the machine. This split is what makes it safe to store API keys and personal model preferences alongside team-enforced permissions.


Precedence

Settings resolve in a fixed order. When the same key appears at multiple tiers, the more specific scope wins:

graph TD
    A["Managed (Org)<br/><i>lowest priority — base policy</i>"] --> B["User (Global)<br/><i>personal defaults</i>"]
    B --> C["Project (Team)<br/><i>team-shared overrides</i>"]
    C --> D["Local (Personal)<br/><i>highest priority — final say</i>"]

Local wins. A developer can always override a project default on their own machine. However, managed (org) settings can mark certain keys as locked, preventing downstream overrides entirely. This is how enterprises enforce hard policy (e.g., disabling network access) while still granting flexibility everywhere else.

In practice, the merge is a shallow merge per section. If your project settings define an allowedTools list and your local settings also define one, the local list replaces the project list — it does not append to it. Plan your settings accordingly.


What Goes Where

Not every setting belongs in every tier. Here is a decision framework:

SettingRecommended TierRationale
Permission allowlists (allowedTools, allowedMcpServers)ProjectTeam-agreed; everyone on the repo should share the same permission surface.
Hook definitions (pre-commit lint, test gates)ProjectDeterministic automation that the whole team benefits from.
Environment variables, API keysLocalSecrets must never be committed. Local is gitignored by default.
Model preferences (model, temperature)LocalPersonal choice; one developer may prefer a smaller model for speed.
Cost/rate limitsUser or LocalPersonal spend controls that vary by individual.
Org compliance rules (audit logging, network restrictions)ManagedNon-negotiable policies pushed by administrators.
Custom slash commandsProject or UserProject-scoped if team-specific; user-scoped if personal workflow.

The guiding principle: if it affects the whole team, commit it. If it is personal or secret, keep it local.


settings.json Structure

Below is a complete annotated example of a project-level settings file (.claude/settings.json in Claude Code). The structure is representative of the pattern across tools, though key names differ.

{
  "permissions": {
    "allowedTools": [
      "Read",
      "Edit",
      "Write",
      "Bash(npm run lint)",
      "Bash(npm run test)",
      "Bash(npm run build)",
      "mcp__github__create_pull_request",
      "mcp__github__list_issues"
    ],
    "deniedTools": [
      "Bash(rm -rf *)",
      "Bash(curl *)"
    ]
  },
  "hooks": {
    "preCommit": [
      {
        "command": "npm run lint --fix",
        "description": "Auto-fix lint issues before commit",
        "blocking": true
      },
      {
        "command": "npm run test -- --bail",
        "description": "Run tests; abort commit on failure",
        "blocking": true
      }
    ],
    "postFileEdit": [
      {
        "command": "npx prettier --write $FILE",
        "description": "Format file after edit",
        "blocking": false
      }
    ]
  },
  "env": {
    "NODE_ENV": "development",
    "LOG_LEVEL": "debug"
  },
  "model": {
    "default": "claude-sonnet-4-20250514",
    "maxTokens": 16384
  }
}

Field-by-field breakdown

permissions.allowedTools — An explicit allowlist of tools the agent may invoke without prompting. Glob patterns (e.g., Bash(npm run *)) are supported. Any tool not on this list will require user confirmation at runtime.

permissions.deniedTools — A denylist that takes precedence over the allowlist. If a tool matches both, it is denied. Use this to carve out dangerous commands from broad globs.

hooks — Named lifecycle events that trigger shell commands deterministically. The agent does not decide whether to run them; the harness executes them unconditionally at the specified point. The blocking flag controls whether a hook failure halts the operation or merely logs a warning.

env — Environment variables injected into the agent’s shell sessions. In a project settings file, these should be non-secret values. Secrets belong in the local settings file.

model — Model selection and parameter overrides. Typically set at the local or user tier so individuals can choose their preferred model without affecting the team.


Context Impact

One of the most important architectural decisions in agentic tools is the split between instruction files and settings files. They serve different purposes and have different costs.

Instruction FilesSettings Files
ExamplesCLAUDE.md, AGENTS.md, codex.mdsettings.json, config.toml
Consumed as context?Yes — injected into the promptNo — parsed as metadata by the harness
Token costProportional to file sizeZero
Agent visibilityAgent reads and follows themAgent does not see them directly
EnforcementSoft — the model may deviateHard — the harness enforces mechanically

This distinction has practical consequences:

The rule of thumb: if a behavior can be expressed as a mechanical gate or trigger, it belongs in settings. If it requires judgment, it belongs in the instruction file.


The Enforcement Model Revisited

The three-layer enforcement model maps cleanly onto the file types:

graph TD
    A["<b>Guidelines</b> — Instruction Files<br/><i>Soft, in-context, costs tokens</i><br/>Prefer named exports over default exports"]
    B["<b>Rules</b> — settings.json hooks<br/><i>Deterministic, harness-executed, zero tokens</i><br/>preCommit → npm run lint --fix"]
    C["<b>Gates</b> — settings.json permissions<br/><i>Hard block, cannot be bypassed, zero tokens</i><br/>deniedTools → Bash(rm -rf *)"]
    A --> B --> C

Guidelines influence the model’s choices. Rules automate deterministic actions. Gates block prohibited actions. A well-configured project uses all three layers together.


Practical Example

Below is a complete project setup showing all three files working in concert. The project is a Node.js API with a PostgreSQL database.

1. Instruction file: CLAUDE.md

This file lives at the repository root and is committed to git. It consumes context tokens and provides the agent with coding guidance.

# Project Context

Node.js REST API using Express and PostgreSQL. TypeScript throughout.

## Architecture

- `src/routes/` — Express route handlers (one file per resource)
- `src/services/` — Business logic (no direct DB access in routes)
- `src/db/` — Knex query builders and migrations
- `src/types/` — Shared TypeScript interfaces

## Coding Standards

- Use early returns to reduce nesting.
- All database calls go through the service layer, never in route handlers.
- Prefer `async/await` over raw Promises.
- Every public function must have a JSDoc comment.
- Error responses use the `ApiError` class from `src/errors.ts`.

## Testing

- Tests live in `__tests__/` directories adjacent to source files.
- Use `vitest` for unit tests, `supertest` for integration tests.
- Every new route handler must have at least one integration test.

2. Project settings: .claude/settings.json

This file is committed to git. Every team member gets these permissions and hooks. It costs zero tokens.

{
  "permissions": {
    "allowedTools": [
      "Read",
      "Edit",
      "Write",
      "Bash(npm run lint)",
      "Bash(npm run lint:fix)",
      "Bash(npm run test *)",
      "Bash(npm run build)",
      "Bash(npm run db:migrate)",
      "Bash(npx tsc --noEmit)",
      "mcp__github__create_pull_request",
      "mcp__github__list_issues",
      "mcp__github__get_issue"
    ],
    "deniedTools": [
      "Bash(npm run db:migrate:rollback)",
      "Bash(rm -rf *)",
      "Bash(DROP TABLE *)",
      "Bash(curl *)",
      "Bash(wget *)"
    ]
  },
  "hooks": {
    "preCommit": [
      {
        "command": "npm run lint",
        "description": "Lint check before commit",
        "blocking": true
      },
      {
        "command": "npx tsc --noEmit",
        "description": "Type check before commit",
        "blocking": true
      }
    ],
    "postFileEdit": [
      {
        "command": "npx prettier --write $FILE",
        "description": "Auto-format on save",
        "blocking": false
      }
    ]
  },
  "env": {
    "NODE_ENV": "development"
  }
}

3. Local settings: .claude/settings.local.json

This file is gitignored. It contains machine-specific values and overrides. Zero tokens, never shared.

{
  "permissions": {
    "allowedTools": [
      "Bash(docker compose *)",
      "Bash(psql *)"
    ]
  },
  "env": {
    "DATABASE_URL": "postgresql://dev:dev@localhost:5432/myapp_dev",
    "OPENAI_API_KEY": "sk-...",
    "GITHUB_TOKEN": "ghp_..."
  },
  "model": {
    "default": "claude-sonnet-4-20250514",
    "maxTokens": 8192
  }
}

At runtime, settings merge in order: managed → user → project → local. The project’s deniedTools still apply even with local additions. Neither settings file appears in the agent’s context — their effect is purely structural. The instruction file focuses exclusively on guidance that requires the model’s judgment.