The terminal agent's configuration lives in two layers. A global file in the user directory holds durable preferences: the model, the approval mode, output formatting. A project file next to the repository is responsible only for permissions - and that is a deliberate limitation rather than an oversight. The configuration root can be overridden by environment variables when needed, which is convenient for containers and test benches.
The asymmetry of the layers is easy to explain. Personal preferences should not be imposed on everyone who cloned the repository: someone else's choice of model or display mode is not a property of the project. Permissions, on the other hand, are exactly a property of the project: which commands count as normal in this repository, what must not be read, where writing is forbidden. That is why the project layer is narrow and devoted only to boundaries.
It helps to see a complete file once. Below are the schema version, the model, the approval mode, editor and display settings and a permissions block with allow and deny rules. It reads as a working profile: what we run, how autonomously, what we show on screen and within which boundaries we act.
The approval mode accepts documented values: working strictly from an allowlist, working with automatic review, and working without restrictions. Those are the same three behavior models as in the editor, and they are chosen the same way - by the cost of a mistake. For automation the strict allowlist variant is sensible: it is predictable and explainable, while a probabilistic assessment in a non-interactive run saves nobody, because there is no one to ask.
The fact that permissions live in the repository has a consequence that makes the narrowness of the project layer worth tolerating. Widening rights becomes a change in a file, which means it lands in the review of changes and in the history. The question of who allowed the agent network access or writes outside the sources, and why, has an answer with a date and an argument attached. Rights that live in someone's home folder give no such answer: everyone has their own setup, and they stop matching within the first month.
Overriding the configuration root with environment variables serves the same purpose - making a run independent of the machine. In a container or on a shared runner the home directory may be someone else's, temporary or missing entirely, and the agent will pick up the wrong file. The symptom is recognizable: locally everything works, on the runner the agent behaves differently - the wrong approval mode, the wrong model. The cure is pointing at the root explicitly rather than adjusting the environment after the fact.
It is worth keeping in mind separately that the set of fields is wider than what is shown. There is an update channel, model parameters, notifications, hints, rewind, next-prompt suggestions, sandbox, network and attribution settings. Listing them all in a book is pointless: the reference changes along with versions. It is more practical to know where to look at the current set - the agent itself has commands to view and update the configuration.
That leads to a general rule for working with a fast-moving product. A static reference is a map, not a contract. Changes in the command line often arrive before the documentation is updated, so before hard-coding a field into the team's shared configuration file it is worth checking how it is named and how it behaves in your version. The price of checking is one command; the price of a mistake is a setting silently ignored for the whole team.
The engineering conclusion is simple: keep the global file personal and the project one shared and narrow. Then cloning the repository gives a colleague exactly the boundaries you meant and does not drag your habits along. And reproducibility in automation is provided not by a file but by explicit flags in the command: they do not depend on what lies in the executor's home directory.
The typical failures are predictable. Putting personal preferences into the project file and being surprised they do not apply. Relying in continuous integration on a machine's configuration instead of explicit flags. Copying someone else's profile whole along with permissions you do not need. And treating the list of fields in a static reference as complete.
// ~/.cursor/cli-config.json
{
"version": 1,
"approvalMode": "allowlist",
"editor": { "vimMode": false },
"display": {
"showLineNumbers": true,
"showThinkingBlocks": false,
"showStatusIndicators": true,
"showStatusLineRunningTime": true
},
"permissions": {
"allow": ["Shell(git)", "Read(src/**)", "Write(src/**)"],
"deny": ["Read(.env*)", "Write(**/*.key)", "Shell(rm)"]
}
}
// approval modes: allowlist, auto-review, unrestricted