Skip to content

settings.json, Field by Field

There are two ways to write about settings.json. One is to list every field in alphabetical order — which the official reference already does, exhaustively and correctly, and it will always be more current than anything we publish.

This is the other way: grouped by what it changes for you, with a clear line between the fields that pay for themselves in the first week and the ones you should leave alone until you have a reason.

Three questions, one answer. This is the decision people get wrong, and no amount of correct JSON survives it.

Who is this for? File In git?
Everyone who clones this repo .claude/settings.json Yes — commit it
Only you, only this repo .claude/settings.local.json No — gitignored
Only you, every repo ~/.claude/settings.json No — lives in your home

Rather than the full field list, here is the grouping that matters when you are deciding what to put in a real project.

Permissions

What Claude may do without asking, and what it must never do. This is the group with the highest payoff and the highest blast radius — worth your attention first.

Hooks

Shell commands fired on lifecycle events. This is how a kit installs itself, how formatting gets enforced, how context gets loaded on every session.

Environment

Variables handed to the session and the commands it runs. Useful for pointing tooling at the right place; dangerous as a home for secrets in a committed file.

Model & behaviour

Which model runs, and how the tool presents itself. Easy to change, easy to over-tune — leave defaults until you have a measured reason.

If you only configure one thing, configure this. It is the difference between approving every ls for a year and approving nothing that matters.

The shape is three lists — what is allowed silently, what is refused outright, and what should always stop and ask:

.claude/settings.json
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff:*)",
"Bash(npm run test:*)"
],
"deny": [
"Bash(rm -rf:*)",
"Read(./.env)",
"Read(./secrets/**)"
]
}
}

Three things worth understanding before you copy that:

  1. deny is the load-bearing list.

    allow saves you keystrokes. deny saves you from a bad afternoon. Write the deny rules first, and write them for the things that are unrecoverable rather than merely annoying — destructive commands and files that hold credentials.

  2. Allow the read-only commands, not the categories.

    Bash(git status) is safe because it cannot change anything. A blanket allow on Bash is not a shortcut, it is the removal of the feature. Grant the specific commands you find yourself approving over and over.

  3. Denying a file is not the same as securing it.

    A deny on ./.env stops the tool from reading it through the normal path. It is a guardrail, not a security boundary — do not treat it as a reason to keep secrets somewhere they would otherwise not belong.

Hooks run shell commands when something happens in the session. They are the mechanism behind almost every “how do I make it always do X” question.

.claude/settings.json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "./.claude/scripts/load-context.sh" }
]
}
]
}
}

Hooks earn a guide of their own — the failure modes are specific enough that a section here would mislead. That one is coming in this same wave.

This is a defensible baseline for a project: no secrets, no machine-specific paths, nothing that assumes your setup.

.claude/settings.json
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"deny": [
"Bash(rm -rf:*)",
"Read(./.env)",
"Read(./.env.*)"
]
}
}

Note what is not there. No model pin, no environment block, no hooks. Every one of those is a decision you should make deliberately later, not inherit from a template today.

Copying a maximal settings file from the internet. You inherit someone else’s risk tolerance and someone else’s machine. Every allow rule you did not write yourself is a rule you cannot justify in review.

Putting secrets in the committed file. Environment blocks look like a convenient home for tokens. They are in git forever the moment you push.

Allowing broad command patterns to stop the prompts. It works, briefly, and it removes the only thing standing between an agent and a destructive command.

Pinning a model and forgetting. A pin that made sense six months ago quietly costs you every improvement since. If you pin, write down why, and revisit it.

Editing while a session is open. Start a fresh session before you decide a change did nothing. This one wastes more time than every other mistake on this list.

  • The file matches its audience: team, local, or user
  • Nothing resembling a secret or a machine path is in the committed file
  • deny covers destructive commands and credential files
  • Every allow entry is a command you actually approved repeatedly
  • No blanket allow on an entire tool
  • Hooks, if any, were reviewed as carefully as CI config
  • The JSON validates
  • A fresh session was started before judging the result