“Add a script to package.json for me” sounds like an object operation. It is not. Agents in Claude Code, Codex, and Cursor read the file as text into context, then swap one unique old string for a new one. If the match is not unique, the dialect is wrong, or an array gets replaced wholesale, the config breaks. This piece is dated 2026-09-16: first the read-write loop the three products share, then Claude Code’s strict settings.json, Codex’s TOML plus repo JSON, and Cursor’s JSONC and checkpoints, then one MCP config sample wired to this site’s tools. For how Skills load on demand, see Why Coding Agents need Skills; for why the bus is JSON, see Why AI likes JSON.
Coding Agents in 2026 already edit repos, run tests, and open PRs. Day to day, what they edit most — and break most — is often not business code, but package.json, tsconfig.json, settings.json, and MCP manifests. Config files are small, keys are dense, dialects do not match: some forbid comments, some live on comments; some arrays are sets, some are ordered steps. The agent has no stable official object API that says “change one key by path.” What it sees is text. Once a text patch drifts, the editor and runtime surface Settings Error on the next second, or worse — silently drop the hooks you already had.
Why config edits are an agent’s first lesson
Business code that is wrong makes tests shout. Config that is wrong is often “won’t start” or “looks fine, the permission array was already overwritten.” Three hard facts:
- Dialect. Strict JSON, JSONC (
//comments, trailing commas), and the occasional JSON5 or single-quoted fake JSON someone dropped in the repo. - Merge semantics. Adding one item to
permissions.alloworscriptsshould append; replacing the whole object deletes a teammate’s rules. - Scope. User-global, project-shared, and machine-local overlay are three files. Edit the wrong one and at best you only affect yourself; at worst you commit a secret path into the repo.
So “can it edit JSON” is not a syntax lesson. It is an agent-workflow lesson: read first, apply the smallest patch, then validate. A Skill tells it when to walk those steps; a Schema tells it whether the object is still legal. For the split, see Agent Skills and Structured Output.
In one sentence. What an agent lacks when it edits config is not “deeper JSON understanding.” It is “is this old text unique, does this file eat comments, and may this array be replaced wholesale.”
Shared loop: discover → read → patch → validate
The three products have different brands. At the file layer they share one loop. Draw that loop once, and the later differences stick.
- Discover. Glob for
**/settings.json,**/mcp.json; Grep for key names. Confirm which file and which scope first. - Read. Read sends numbered text into context. Editing without a prior read is the move all three products try to block: the content may be stale, and the patch lands on a hallucination.
- Patch. Default is Edit / StrReplace:
old_stringmust appear in the file exactly once. Whole-file Write is for new files, or when the old structure can no longer take a local replace. - Validate.
JSON.parse, editor diagnostics, Schema, related commands (npm pkg get,claude /status). On failure, Read again. Do not stack patches.
This is the same budget thinking as Function Calling / MCP: keep the tool definition short, then validate the result. The config file itself is the disk form of the tool contract.
Three dialects: strict JSON, JSONC, fake JSON
The miss an agent makes most often is not “forgot a key.” It is “which syntax does this file actually eat.”
| Dialect | Comments / trailing commas | Common files | Typical break when the agent writes badly |
|---|---|---|---|
| Strict JSON | Neither allowed | package.json, Claude Code settings.json, most MCP manifests |
Settings Error on startup; JSON.parse fails |
| JSONC | Allows //, /* */, often trailing commas |
VS Code / Cursor settings.json, some tsconfig.json |
Whole-file rewrite as strict JSON wipes comments and key order |
| Fake JSON / JSON5 | Single quotes, unquoted keys | “Samples” copied from docs or chat | Looks like an object; every strict parser rejects it |
One key does not open three locks. Before the agent edits, name the dialect in the prompt. Afterward, accept with the matching parser, not with “looks like JSON.”
Claude Code: Read first, then exact Edit
Claude Code’s file tools are a trio: Read, Edit, Write. Official behavior folds into three hard rules:
- Prefer
Editon existing files. It only submits the changed slice, which lowers accidental overwrite. Editis a literal replace, not a regex. Whitespace, indent, and newlines must match;old_stringappearing twice withoutreplace_allfails.- In the session you must
Readthat path beforeEditor an overwritingWrite. If the disk changed after the last read, the tool rejects the stale patch.
The config itself is JSON. User default lives at ~/.claude/settings.json, project-shared at .claude/settings.json, machine overlay at settings.local.json. These files are strict JSON: one // line or one trailing comma, and the next startup is Settings Error. Official docs also stress: when you change permission arrays or hooks, merge existing items — do not replace the whole object. Afterward, /status shows which layers loaded; most keys hot-reload, so you do not restart just to add one allow rule.
{
"permissions": {
"allow": [
"Bash(npm test *)",
"Read(src/**)"
],
"deny": [
"Read(.env*)",
"Edit(.env*)",
"Write(.env*)"
]
}
}
Permission rules are structured strings too: Read(path), Edit(path), Write(path). Denying Read also blocks Edit / Write on the same path. That is the config file constraining how the agent edits other config — JSON is both the object being changed and the contract for rewrite permission.
You can pin the Claude Code prompt. “Read the target settings first; Edit only the keys you mean to change; merge arrays; confirm it is still strict JSON.” The community update-claude-code-config Skill is basically those sentences folded into a handbook.
Codex: its own config is TOML; the repo is still JSON
Easy to mix: in Codex CLI / ChatGPT’s Codex, user config is not JSON. Global lives at ~/.codex/config.toml; a trusted project can stack .codex/config.toml; one-shot overrides use -c key=value, values parsed as TOML. Process notes go in AGENTS.md; reusable steps go in a Skills directory. Project-level TOML cannot change credentials, provider, notifications, or other machine-local keys.
But once Codex enters the repo as a coding agent, it still faces JSON:
package.json,tsconfig.json, frontend toolchain config;- MCP / tool manifests, OpenAPI, model catalogs (
model_catalog_jsonpoints at a JSON file); - The agent’s own structured output: the CLI can point
--output-schemaat a JSON Schema and validate the final reply shape.
Sandbox defaults decide whether it can write: read-only is read-only; workspace-write can change workspace files. So “how Codex edits JSON” splits into two layers — its own knobs live in TOML, editing your repo still goes through read-file + patch. Do not treat a table in config.toml and an object in package.json as the same contract.
As with Claude Code, rewriting a whole repo JSON is the worse move. Codex also has an accept hook: you can write “must validate against Schema after the edit” as a Skill, or hand the expected shape to --output-schema. The matching tool pages are Structured Output and MCP Tool.
Cursor Agent: patches, checkpoints, and settings.json
Cursor Agent’s loop is the same: search → Read → exact replace / Write → command accept. The desktop client adds two things that are especially useful for config:
- Checkpoints. Automatic snapshots before a large edit, separate from Git — useful when “this settings change blew up; go back to the previous version in the session.”
- JSONC. User and workspace
settings.jsonallow comments. A good Skill (for example the community update-cursor-settings) requires: read first, keep unused keys and comments, change only the named paths, and confirm the editor can still parse after write-back.
Project-level files often include .cursor/mcp.json and .vscode/settings.json. The first is usually strict JSON for the MCP runtime; the second is JSONC. If the agent writes both back with the same “strict stringify,” it gets MCP right and wipes workspace-setting comments — or the other way around, adds // to the MCP manifest, and the runtime rejects it on the spot.
CVE-2025-49150 in 2025 pushed this onto the security layer: the agent can edit JSON itself. If json.schemaDownload.enable is true, a $schema in the file fires an outbound HTTP GET with no confirmation. Cursor therefore turns remote Schema download off by default. A trusted project can enable it only in .vscode/settings.json — do not enable it globally. For readers of this site, the conclusion is concrete: $schema is also a field the agent can write. It is not only an editor hint. It is an outbound request.
Ask / Plan / Agent. If you only want to know why tsconfig is written that way, use Ask. If you need to change several config files, Plan first, then let Agent apply patches. Config tasks injure neighboring keys more easily than editing one function.
Data structures: objects, arrays, pointers, patches
The agent uses text patches. In your head, still think structured data. Four layers are enough:
| Layer | What it is | How to use it when editing config |
|---|---|---|
| Object | Key-to-value map | Merge keys: leave unmentioned fields alone |
| Array | Ordered list; some are really sets | Append / dedupe; do not replace the whole array unless you truly want to empty it |
| JSON Pointer | e.g. /permissions/deny/0 |
Name “change this path” in the prompt so the agent does not pick a sibling key |
| JSON Patch | RFC 6902 add / remove / replace | Mental model: one operation at a time. Most agents will not emit a standard Patch, but you can accept against this shape |
A change you want the agent to make can be written as an object first, not a paragraph:
{
"file": ".claude/settings.json",
"dialect": "strict-json",
"op": "add",
"path": "/permissions/deny/-",
"value": "Read(.env*)",
"merge_arrays": true
}
A - at the end of path means append to the array. That is JSON Patch semantics. You can fold this intent into a Schema with this site’s tools first, then paste it into the conversation as the accept contract. The agent still lands it with a text Edit, but you know what success looks like.
Merge keys, or rewrite the whole file
The Write tool overwrites the whole file. For a new 20-line MCP manifest, that is fine. For a package.json that has lived two years, that is an incident.
- Use Edit. Add one script, add one deny rule, change one port, add one key under
compilerOptions. - Use Write. The file does not exist yet; the old structure can no longer take a local replace (for example a flat config migrating to a nested schema); you explicitly asked for “replace the whole file with this sample,” and you already have a backup.
- Always Read first. Write without a prior read is the model memory overwriting the disk.
Array merge is the most frequent failure. Below, the first block is a correct append; the second eats a teammate’s allow rules:
// Wanted: read the old array, add one item "allow": [ "Bash(npm test *)", "Read(src/**)", "Bash(npm run lint *)" ] // Don't: replace the whole array with only the new item "allow": [ "Bash(npm run lint *)" ]
Writing “merge arrays, do not replace” in the prompt is more useful than “be careful.” If it can be a Skill, do not retype it every time.
Common failures
- The patch is not unique.
"strict": trueappears twice in the file; Edit fails or hits the wrong block. Carry a few more lines of context. - The dialect is reversed. Comments added to Claude Code settings; or Cursor settings comments all stringified away.
- Trailing commas. JSONC survives them;
package.jsonand Claude settings do not. - Key order and blank lines. A whole-file rewrite turns the Diff into “the entire file is red”; review cannot see what actually changed.
$schemaoutbound. A Schema URL the agent can write will trigger a download in some editors. Do not enable global remote Schema on an untrusted repo.- Wrong scope. A machine-local absolute path written into project
settings.jsonand committed; or a user-global change nobody else can reproduce. - Edit without validate. The editor may hot-reload a failed config and silently fall back. Parse after the change. Do not stop at “the tool call succeeded.”
One MCP config sample you can accept locally
The object below is both “intent” and “shape after it lands.” Parse it, grow a Schema, and Diff it in the local tools first, then hand it to any of the three agents to edit the real file in the repo.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "./data"],
"env": {
"MCP_LOG_LEVEL": "info"
}
}
},
"permissions": {
"allow": [
"Read(./data/**)",
"Edit(./data/**)"
],
"deny": [
"Read(./data/secrets/**)",
"Write(./data/secrets/**)"
]
},
"meta": {
"dialect": "strict-json",
"merge_arrays": true,
"scope": "project"
}
}
With this site’s inference rules, that sample yields:
| Field | Infer types | What to do after generate |
|---|---|---|
mcpServers / env / meta |
object | Think through additionalProperties: new server names are open; meta.dialect should collapse to an enum |
command / MCP_LOG_LEVEL / scope |
string | Use user | project | local for scope; do not leave “just change it on my side” |
args / allow / deny |
array of string | Declare “append” vs “replace”; default to append |
merge_arrays |
boolean | Must be true / false, not “keep as much as you can” |
Treat the same object as the agent’s accept contract: after the disk file changes, the key set, array items, and dialect still have to line up. Once the sample is ready, open JSON Format to confirm it parses, then use JSON → Schema to grow a first-draft contract.
How to accept it in JSON Toolbox
- Paste the existing config or the sample above into JSON Format. If strict JSON fails, go through JSON Clean first; for JSONC, strip comments before parse.
- Use JSON → Schema or Structured Output to generate contracts for the “intent object” and the “landed config.”
- Take the second file after the agent edited it to Schema validation. Watch whether arrays were appended or replaced, and whether an enum drifted into natural language.
- Diff it against the copy from before the edit with JSON Diff. An ideal Diff only touches the paths you named; if the whole file goes red, it was rewritten with Write.
- If the config will go to MCP / Function Calling, run the same fields through MCP Tool and Function Calling separately — do not copy two sets of key names.
Everything is processed in the browser and is never uploaded to a server. The cloud or local agent applies the patch; the local tools lock the shape and the contract before the patch enters Git.
FAQ
Do agents edit JSON as objects, or as text?
As text. Read first, then exact-string replace or whole-file Write. There is no stable official “change one key by JSON Pointer” API. The patch must be unique, and the result must parse.
How do Claude Code, Codex, and Cursor differ when they edit JSON?
The loop is the same. Claude Code settings are strict JSON and require read-then-edit. Codex keeps its own knobs in TOML; repo JSON still goes through file patches. Cursor settings are JSONC, with session checkpoints, and remote Schema download off by default.
Can settings.json have comments?
Cursor / VS Code can (JSONC). Claude Code settings cannot. Wrong dialect, and the agent either fails startup or wipes the comments.
Why not let an agent rewrite the whole package.json?
Write overwrites the whole file; scripts, overrides, key order, and hand-written comments can all vanish. Replace only a unique slice, merge arrays, then accept with Diff.
How do you keep an agent from breaking config?
Name the scope; read then edit; ban Write unless needed; parse / Schema / Diff afterward; put sensitive paths in deny. If it can fold into a Skill and a Schema, do not rely on a spoken “be careful.”
Summary
AI coding agents read and edit JSON config not through an object database, but through a text loop you can retell: discover the file, read it into context, patch a unique slice, then parse and validate. Claude Code writes “Read then Edit” into the tool contract and pins settings to strict JSON; Codex parks its own knobs in TOML, still patches your repo JSON the same way, and can fold the final reply into a Schema; Cursor adds checkpoints and JSONC, and also the $schema outbound security bill. For developers, the next step is not making the model “understand JSON better.” It is folding dialect, scope, and merge rules into a validatable object — short index, small patch, hard contract. The same MCP sample can generate a Schema, run a Diff, then land with any of the three.