Skip to content

Configuration & profiles

You wanted to change the default model to the cheaper one. Twenty minutes later you have three browser tabs open and a list of places the setting could live: a project file, a user file, an environment variable, a profile, a managed policy, a flag. You edited one of them. It is not the one that is winning. Nothing in the TUI tells you which one is.

The second version of this chore is swapping whole setups. A locked-down posture for client repos, a wide-open one for personal projects, and a hand-edit of the settings file every time you cross between them.

Both problems come from the same place: the CLI’s settings are not one file, they are a stack of files with a resolution order, and the order is invisible until you go looking. Learn the stack and the twenty-minute hunt becomes a two-minute check, and the hand-edit becomes codex --profile safe.

The shape is broadly shared, but the layers are not. Most tools have project and user configuration; managed or organization policy is product-specific, and Pi has project trust rather than a native managed-policy layer. These files hold settings that don’t belong in a prompt or a skill: the default model, permission rules, MCP server registrations, hook bindings, theme, and output defaults.

Real-world examples of what teams put in configuration (vs. in code or skills):

  • Default model - pin one for the project so contributors don’t each use whatever they happen to default to. See Model selection.
  • Permission policy - the team’s allow/ask/deny rules, checked in so everyone gets the same guardrails. See Permissions.
  • MCP server registrations - your repo’s Postgres/Linear/Figma adapters wired up once. See MCP servers.
  • Hook bindings - declarative event handlers such as lint-after-edit or block-pushes-to-main. See Hooks.
  • Personal overrides - machine-specific quirks kept out of git: a different model, a noisier hook, an extra debugging MCP server.
  • Org policy - managed config that mandates a rule and cannot be overridden downstream: deny Bash(rm:*), force a specific model.
  • Profile bundles - --profile work (sandboxed, conservative) versus --profile yolo (full access, never asks), swapped by flag rather than by edit.

The test: if the same setting is being typed at the CLI more than once, or copy-pasted between files, it belongs in configuration.

Each CLI’s full config surface as one tree. Click any entry for what it does, when it loads, whether it belongs in git, and a working example.

committedin git, shared with your teamgitignoredlocal only, never sharedauto-generatedwritten by the tool; do not hand-edit
your-project/
~ (home directory)
Select a file or folder

Every entry shows what the file does, when Claude Code reads it, whether it belongs in git, and a working example.

This is the part that costs the twenty minutes, and it is the one place the tools genuinely diverge rather than just renaming things.

The intuition most people bring is that the more specific file wins: project beats user, because the project is closer to the work. On the precedence question that intuition mostly holds. What breaks it is a different mechanism entirely, and it’s the one worth learning.

Tool Order when project and user config disagree Precondition that can void the project file entirely
Claude Code Managed policy, then CLI flags, then project-local, then project-shared, then user last -
Codex Project config is merged when the project is trusted; user and managed settings also participate Directory trust: config in an untrusted project directory does not apply at all
OpenCode Project, merged over global rather than replacing it; managed layers above -
Cursor VS Code settings plus Cursor project files; the CLI also has project-scoped .cursor/cli.json configuration Persistent CLI permissions are project-scoped, while per-run flags such as model and output format are separate
Copilot VS Code’s settings.json model, with github.com org policy above it Org policy can exclude models and content outright, whatever the repo says
Pi Project, deep-merged into global key by key, so a project overrides only what it names Trust gate: .pi/ resources load only after the project is trusted

Two things to take from the table. First, the real divergence is merge-versus-replace, not order. Knowing that project outranks user tells you less than you’d think, because the tools disagree about what “outranks” does to the rest of the file.

Pi deep-merges key by key, so a project file overrides only the keys it actually names and everything else falls through to your global settings. OpenCode likewise merges over global rather than replacing it. Where a tool replaces instead of merging, a project file that sets one key can silently drop every other setting you had. That, not the ordering, is what produces the setting that “isn’t winning” for no visible reason.

Second, a trust gate is not precedence. It is a switch that turns the project layer off entirely. A Codex or Pi project file in an untrusted directory is not losing a merge, it is never being read. From the outside the two failures look identical, and they are fixed differently: one by editing a file, the other by trusting the directory.

A practical consequence of Claude Code’s order: .claude/settings.json is checked in and beats your personal ~/.claude/settings.json, so a teammate’s committed default really does override yours. If you need a personal override to stick, .claude/settings.local.json sits above it and stays out of git.

For Codex, the current config reference is the source of truth: project config is conditional on trust, and profile files are versioned configuration details rather than a portable convention.

You want to… Reach for Not
Pin defaults that survive across sessions (model, theme, output format) Configuration file Env vars per session
Share team settings via the repo Project config (checked in) User config
Keep personal overrides off git A gitignored local-settings file or shell env Project config
Swap between named setups with one flag Codex profile Editing the file each time
Distribute config + skills + hooks + MCP as one unit Plugin Raw config
Enforce org-wide policy that can’t be overridden Managed config Project / user files
Give one agent a different posture than another (OpenCode) Custom primary agent A second profile

Configuration is for defaults and policy. Skills, hooks, MCP servers, and subagents are still where the behaviour lives; config just enables them and sets their defaults. If your config file is growing into a procedure, you wanted a skill.

Showing: Claude Code pick a tab to change

Locations:

  • .claude/settings.json - project (checked in)
  • .claude/settings.local.json - project (gitignored, your local overrides)
  • ~/.claude/settings.json - user
  • Managed-policy settings.json - org-controlled

Layering: managed > CLI > project local > project shared > user. More-specific settings and explicit CLI flags can change the effective configuration; each level can set permissions, hooks, MCP servers, skill overrides, and other knobs.

Settings categories:

  • permissions - see Permissions
  • hooks - see Hooks
  • skillOverrides - hide or expose specific skills without editing them
  • model - default model for this scope
  • Misc TUI knobs (theme, status line)

Claude Code does not have named profiles. Each scope’s settings.json is the only config for that scope at a time. To swap configs, you change the file (or move it).

Aspect Claude Code Codex OpenCode Cursor Copilot Pi
Project config .claude/settings.json .codex/config.toml (in trusted dirs) opencode.json .vscode/settings.json + .cursor/ (rules, mcp, hooks, etc.) .github/copilot-instructions.md, .github/instructions/, .vscode/mcp.json .pi/settings.json
Local-only project config .claude/settings.local.json (gitignored) - - - - -
User config ~/.claude/settings.json ~/.codex/config.toml ~/.config/opencode/opencode.json Cursor/User/settings.json (platform path) + ~/.cursor/ VS Code user settings.json + ~/.copilot/ (CLI) ~/.pi/agent/settings.json
Format JSON TOML JSON JSON (VS Code) + assorted files under .cursor/ JSON (VS Code settings.json) JSON - deep-merges user + project; also models.json, trust.json
Managed/org overrides Yes Yes (managed configs) Yes (managed configs) Yes (Team / Enterprise dashboard) Yes (github.com org policy, Business/Enterprise) -
Named profiles No Yes (--profile) 1 No (use primary agents) No No No
Config-per-project limits - Project config requires directory trust - CLI: permissions only, rest is global Org policy overrides repo files Project resources require trust

1 Codex profiles are the one named-profile mechanism in scope, and the only place a whole posture swaps by flag. Note the vocabulary trap: “profile” means a named config bundle in Codex and nothing of the sort elsewhere - Claude Code and OpenCode both say “settings level” or “scope” for what looks superficially similar.

The current Codex config reference documents profile files next to the main config as $CODEX_HOME/profile-name.config.toml, selected with --profile. Do not copy older [profiles.<name>] examples without checking the versioned reference first.

Everything above is a default. The next time a setting doesn’t take effect, work the stack rather than the file: confirm the project directory is trusted, check whether your user config is shadowing the repo’s, and only then edit. Most of the twenty minutes is spent editing the wrong layer confidently.