settings.json is the main settings file, and a full index of all keys is placed in a separate reference at the end of the book. Here something else matters more - seeing several whole profiles, because settings work not by a single key but as a coherent set. A profile answers the question "how do we want Claude to behave in this context" and is assembled from permissions, sandbox and utility keys into one explainable behavior, not a scattering of copied lines.
A base team profile sets a sensible working balance. The default mode is default; in allow are exactly the families of verification commands (lint, test, typecheck); in ask are push and leaving the sandbox; in deny are reading .env and secrets. Alongside is an enabled sandbox in strict mode with a narrow network allowlist, plus the utility cleanupPeriodDays and respectGitignore. It helps to see such a profile in full once - it reads as an explicit description of the team's working boundaries.
A caveat to any profile is mandatory: the commands and domains in examples are only form, not a ready allowlist. They are replaced with the project's real toolchain; copying someone's allowlist mechanically is dangerous, because it allows exactly the commands and hosts another project needed, not yours. A profile is a template of structure that you fill with your values, aware of what exactly each line opens.
Automation needs a stricter profile. A locked-down profile sets the dontAsk mode, allows only reading and a narrow set of commands (npm ci, lint, test) and explicitly forbids Edit, Write, WebFetch and WebSearch, keeping the sandbox strict. This is a profile for a closed non-interactive run, where the agent must neither change files nor go to the network. One-off behavior is refined with flags: --settings passes a path or a JSON override, --setting-sources limits which categories of sources load at all.
Understanding what applies live matters for safe editing. Claude Code watches user, project, local and managed settings; changes to permissions, hooks and credential helpers usually take effect in the current session and trigger the ConfigChange hook. But an already running process does not get the new policy retroactively - you check the next action rather than hope the edit catches up with an ongoing operation. This is exactly the same logic as with the context cache.
You verify settings with a set of commands, not by eye. /config opens the user interface settings, /status shows the resolved provider, model and sources, /doctor diagnoses the configuration, /permissions the resolved rules, /sandbox the sandbox state and overrides. It helps to gather these commands side by side once: any settings edit is checked with exactly them - through the actual effect and a negative test (that a deny really forbids), not through confidence that it is "written correctly".
/config # user interface settings
/status # provider/model/settings sources
/doctor # configuration diagnostics
/permissions # resolved permission rules
/sandbox # sandbox state and overridesRare switches need separate attention. Some keys and variables are meant for diagnostics, enterprise rollout or temporary compatibility, and the presence of an entry in the reference does not mean a recommendation to enable it. Be especially careful with fields containing the words dangerously, disable, bypass, allowAll, experimental and feature flag: before enabling, find the official task, the version it appeared in and the way to roll back. A flag with "dangerously" in its name is named so deliberately.
The typical settings failures are predictable. Copying someone's allowlist mechanically, opening commands and hosts your project does not need. Enabling a rare switch by its name without finding its source and cost. Expecting an edit to catch up with an already running process. And not verifying the effect with a negative test. Assemble settings as a coherent profile for the context, change through a separate check and verify the actual state via /status, /doctor and /permissions.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "default",
"allow": ["Bash(npm run lint:*)", "Bash(npm run test:*)", "Bash(npm run typecheck:*)"],
"ask": ["Bash(git push:*)", "Bash(dangerouslyDisableSandbox:true)"],
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
},
"sandbox": {
"enabled": true, "autoAllowBashIfSandboxed": true, "allowUnsandboxedCommands": false,
"network": { "allowedDomains": ["registry.npmjs.org"] }
},
"cleanupPeriodDays": 30,
"respectGitignore": true
}
// commands/domains are only form; replace with your real toolchain