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.
Every file, and when it loads
Section titled “Every file, and when it loads”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.
When two files disagree
Section titled “When two files disagree”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.
Why this and not…
Section titled “Why this and not…”| 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.
How it works in each tool
Section titled “How it works in each tool”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 Permissionshooks- see HooksskillOverrides- hide or expose specific skills without editing themmodel- 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).
Locations:
.codex/config.toml- project (gated by directory trust)~/.codex/config.toml- user- Managed config - org-controlled
Project-level config only applies if the directory is marked trusted. Trust is configured in TOML via:
[projects."/abs/path/to/repo"]trust_level = "trusted"Named profiles are Codex’s distinguishing feature. Each profile is its own file in $CODEX_HOME, named <profile-name>.config.toml:
sandbox_mode = "workspace-write"sandbox_mode = "danger-full-access"approval_policy = "never"Then: codex --profile work or codex --profile yolo.
Plenty of older examples show profiles as inline [profiles.<name>] tables inside a single config.toml. That is the previous format - use the separate-file form above.
Feature flags: the [features] table toggles optional and experimental capabilities (set feature_name = true).
Locations:
opencode.json- project~/.config/opencode/opencode.json- user (global)OPENCODE_CONFIG/OPENCODE_CONFIG_DIRenv vars for custom paths- Managed/system config for org-wide policy
Layering: sources are merged, not replaced. Among the standard config files, project config has the highest precedence and overrides global/remote where they overlap. Managed config layers above that.
No named profiles in the Codex sense - per-agent customisation (via primary agents) substitutes for many of the same use cases. You’d define a “yolo” primary agent rather than a “yolo” profile.
Cursor inherits VS Code’s settings model. User settings live in the platform-specific Cursor/User/settings.json (e.g. ~/Library/Application Support/Cursor/User/settings.json on macOS). Project settings in .vscode/settings.json are honoured.
Cursor-specific surfaces:
.cursor/at the repo root holds Cursor-specific assets:rules/,commands/,skills/,agents/,mcp.json,hooks.json,environment.json.~/.cursor/holds the user-scope equivalents.- CLI config has a notable constraint: only permissions can be set per-project; all other CLI settings are global.
- Team / Enterprise config is pushed from the Cursor dashboard, including Team Rules and Team Commands.
Environment variables: CURSOR_API_KEY is documented for headless / CI auth. Other variables are surface-specific; use the current CLI parameter reference before relying on one.
Cursor does not currently have a single canonical cursor.config.json analogous to opencode.json; configuration is spread across several files in .cursor/ plus VS Code’s settings.json. No named profiles in the Codex sense.
Copilot rides VS Code’s settings.json. Two namespaces dominate: github.copilot.* and chat.*.
Key VS Code settings:
github.copilot.chat.codeGeneration.useInstructionFiles- auto-applycopilot-instructions.mdchat.instructionsFilesLocations- where to find.instructions.mdfiles (default includes.github/instructions)chat.promptFilesLocations- prompt-file directories (default.github/prompts)chat.agentSkillsLocations- extra skill directorieschat.useCustomizationsInParentRepositories- walk up for monorepo parent configschat.agent.enabled- toggle agent mode (VS Code 1.99+)chat.agent.sandbox.enabled- sandbox shell callschat.mcp.access,chat.mcp.discovery.enabled,chat.mcp.apps.enabledchat.permissions.default,chat.tools.*.autoApprovechat.planAgent.defaultModel,inlineChat.defaultModel,chat.utilityModel,chat.utilitySmallModel
Repo-level config:
.github/copilot-instructions.md,.github/instructions/,.github/prompts/,.github/skills/.vscode/mcp.json(VS Code MCP)
Org-level (github.com → Org Settings → Copilot, Business/Enterprise):
- Custom instructions, model allow/deny, content exclusions, MCP policy, extension/plugin policy.
No named profiles à la Codex are described here. JetBrains and other IDEs have plugin-specific preference panes; parity with VS Code’s settings catalog is surface-dependent, so verify an individual setting in that IDE’s current documentation.
Global config, all under ~/.pi/agent/:
settings.json- defaults and behaviormodels.json- custom providers/modelsauth.json- credentialstrust.json- which project directories are trusted
Project config: .pi/settings.json deep-merges into the global settings, key by key, nested - a project only needs to specify the keys it overrides.
Common settings keys: defaultProvider, defaultModel, defaultThinkingLevel, enabledModels, defaultProjectTrust, plus compaction behavior and resource-path arrays for packages, extensions, skills, prompts, and themes.
Trust gate: project-local .pi/ resources (settings, extensions, skills) only load after the project is trusted. Trust decisions are stored in ~/.pi/agent/trust.json, so you’re asked once per project, not once per session. Pass -a / --approve or -na / --no-approve to set trust for a single run without touching the stored state - useful for CI (see Headless & CI) or one-off checks on an unfamiliar repo.
Comparison
Section titled “Comparison”| 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.