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.

ToolOrder when project and user config disagreePrecondition that can void the project file entirely
Claude CodeManaged policy, then CLI flags, then project-local, then project-shared, then user last-
CodexProject config is merged when the project is trusted; user and managed settings also participateDirectory trust: config in an untrusted project directory does not apply at all
OpenCodeProject, merged over global rather than replacing it; managed layers above-
CursorVS Code settings plus Cursor project files; the CLI also has project-scoped .cursor/cli.json configurationPersistent CLI permissions are project-scoped, while per-run flags such as model and output format are separate
CopilotVS Code’s settings.json model, with github.com org policy above itOrg policy can exclude models and content outright, whatever the repo says
PiProject, deep-merged into global key by key, so a project overrides only what it namesTrust 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 forNot
Pin defaults that survive across sessions (model, theme, output format)Configuration fileEnv vars per session
Share team settings via the repoProject config (checked in)User config
Keep personal overrides off gitA gitignored local-settings file or shell envProject config
Swap between named setups with one flagCodex profileEditing the file each time
Distribute config + skills + hooks + MCP as one unitPluginRaw config
Enforce org-wide policy that can’t be overriddenManaged configProject / user files
Give one agent a different posture than another (OpenCode)Custom primary agentA 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.

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).

AspectClaude CodeCodexOpenCodeCursorCopilotPi
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.jsonCursor/User/settings.json (platform path) + ~/.cursor/VS Code user settings.json + ~/.copilot/ (CLI)~/.pi/agent/settings.json
FormatJSONTOMLJSONJSON (VS Code) + assorted files under .cursor/JSON (VS Code settings.json)JSON - deep-merges user + project; also models.json, trust.json
Managed/org overridesYesYes (managed configs)Yes (managed configs)Yes (Team / Enterprise dashboard)Yes (github.com org policy, Business/Enterprise)-
Named profilesNoYes (--profile) 1No (use primary agents)NoNoNo
Config-per-project limits-Project config requires directory trust-CLI: permissions only, rest is globalOrg policy overrides repo filesProject 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.