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

JSON is not TypeScript: the one-line bug that failed ten edits

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.


Ready to try your idea?

Start with a clear description. AppX will help you turn it into an app you can test.

Start describing your app →

← Back to Blog