JSON is not TypeScript: the one-line bug that failed ten edits
For a week, every edit that touched app.json failed with a syntax error. The JSON was valid. The validator was parsing it as TypeScript. How I found it from failure codes, and the second bug hiding behind it.
Ilyos Olimov · · 4 min read

On October 17, three days into an ad test, I was counting failed builds in the database every evening. The number that stuck out: 13 failed edits in 24 hours from brand-new users, and 10 of them had the same shape.
Terminal code: MODIFY_GENERATION_FAILED. Reason: CHECKPOINT_PROTOCOL_REJECTED. Verdicts: syntax, protocol, protocol. Files: app.json, package.json, eas.json.
Those three files are not code. They are configuration. And they were valid.
How an edit is checked
When the AI proposes an edit, AppX does not trust it. Every file in the proposed change goes through a gate before it reaches the sandbox: is the path allowed, is the content well formed, does it parse. A file that fails the gate rejects the whole edit, and the user is refunded. That gate has saved a lot of broken builds.
The syntax part of that gate uses the TypeScript compiler to parse the file and count diagnostics. To do that it has to tell the compiler what kind of file it is looking at. The function that decides was simple:
function scriptKind(path: string): ts.ScriptKind {
if (path.endsWith('.tsx')) return ts.ScriptKind.TSX;
if (path.endsWith('.jsx')) return ts.ScriptKind.JSX;
if (path.endsWith('.js')) return ts.ScriptKind.JS;
return ts.ScriptKind.TS;
}
Anything not in the list is TypeScript. Including app.json.
Parse {"expo": {"name": "Reps"}} as a TypeScript file and the compiler gives you three diagnostics. It thinks you wrote an object literal as a statement. Three diagnostics means "syntax", and "syntax" means rejected.
I checked it in one line of node: the same content parsed as JSON gives zero diagnostics, parsed as TS gives three. That was the whole bug.
Why it took a week to notice
The line was added on September 23, as part of making the gate stricter. The tests covered TypeScript and JavaScript files, because that is what the gate was for. Nobody wrote a test for a JSON file, because nobody thought of JSON as something the gate would even look at.
And the failure was silent from the user's side. The edit "failed", credits came back, the chat showed a generic error. Nobody emails you about that. They try once more and leave. I only saw it because the ad test pushed enough new users through the edit path in one week to make the pattern visible in the failure codes.
Renaming an app is the most common first edit people make. Renaming an app touches app.json. So the most common first edit was failing for everyone.
The fix
if (path.endsWith('.json')) return jsonSyntaxInspection(content);
function jsonSyntaxInspection(content: string) {
try { JSON.parse(content); return null; }
catch { return { diagnostic: 'syntax' }; }
}
Six lines, plus six tests: three valid JSON files, three broken ones. The TypeScript path is untouched.
The second bug behind it
While reading the forge logs for the failed edits I found a different failure with a similar face. If a build was stopped by the user during validation, or failed a strict type check, the sandbox was left holding the rejected file tree, while the database still pointed at the last accepted one. Every later edit then failed before validation with "base revision mismatch", and that failure was not even logged on the API side.
The fix there is a small self-heal: before syncing an edit, check the sandbox's revision marker against the accepted base, and if they differ, re-materialise the accepted revision first. It adds about a second to an edit and removes a whole class of "my app is stuck" reports.
Both fixes shipped on October 18. The proof was an edit that renames an app, which touches app.json: 40 seconds, promoted, published. The next day the same kind of edit took 32.
The lesson I keep relearning
A strict gate is only as good as its list of file types. When you make validation stricter, add a test for the file you did not think of. For me that file was the one every app has.
Keep going
More practical ways to move your app forward.
- How to describe your app to AI: a prompt template
Write a clear AI app-builder prompt with a copyable template, a practical example, acceptance checks, and focused follow-up edits.
- How to turn a client brief into a mobile app prototype
Turn a client brief into a mobile app prototype with a copyable AppX prompt, a practical test checklist, and a focused client-feedback workflow.
- An Uzbek AI assistant prototype in ten minutes
A user's prompt, one plan, 26 credits, ten minutes. What came out, what is real, what is honestly a placeholder, and why the prompt worked.
- Do not pretend the feature works
A user wrote one line in his prompt that every prototype should carry: do not pretend that AI voice, memory or autonomous actions work unless they are actually implemented. Why an honest prototype is worth more than an impressive one.
- You can build your app with a coding agent. Here is why most founders should not.
Coding agents in the terminal are the best thing that happened to developers this decade. For a founder with no developer, they are a maze with no map. What the first week looks like with an agent, what it looks like with AppX, and when you should still pick the agent.
Ready to try your idea?
Start with a clear description. AppX will help you turn it into an app you can test.