What Happens When JSON Parsing Fails During ADHD Phases

When JSON parsing fails during ADHD phases, the engine catches the error and returns a safe fallback for that specific phase, allowing the rest of the pipeline to continue without crashing.

The ADHD engine in the UditAkhourii/adhd repository relies on Large Language Model (LLM) outputs that must be converted from raw text into structured JavaScript objects. Because LLMs can return malformed JSON, extra prose, or unexpected structures, the codebase implements a fail-open strategy in src/engine.ts that keeps the workflow alive even when JSON parsing fails during ADHD phases.

The Fail-Open Architecture Behind src/engine.ts

The core helper parseJSON is defined in src/llm.ts and is responsible for turning LLM text into typed objects. Rather than letting parsing exceptions bubble up and halt execution, each phase in src/engine.ts wraps its parseJSON call in a dedicated try / catch block.

This design means one malformed LLM response cannot abort the entire run. According to the UditAkhourii/adhd source code, every fallback returns a valid data shape that matches the expected TypeScript typings, ensuring downstream phases receive predictable inputs.

Phase-by-Phase Fallbacks When JSON Parsing Fails During ADHD Phases

When JSON parsing fails during ADHD phases, the engine handles each cognitive phase differently based on what that phase produces.

Reframe Phase: Preserve the Original Problem

In reframeProblem (src/engine.ts, lines 33-41), a parse error triggers a return of the original problem unchanged alongside changed: false.

// src/engine.ts#L33-L41
try {
  const reframed = parseJSON(raw, ReframeSchema);
  return { problem: reframed.problem, changed: true };
} catch {
  return { problem: originalProblem, changed: false };
}

This fallback guarantees that later branches still see a sensible prompt even when the reframe attempt produces garbage JSON.

Diverge Phase: Yield an Empty Idea List

The divergeBranch function (src/engine.ts, lines 68-73) returns an empty idea list for the affected frame when parsing fails.

// src/engine.ts#L68-L73
try {
  const rows = parseJSON(raw, DivergeRowSchema);
  return { frameId: frame.id, ideas: rows };
} catch {
  return { frameId: frame.id, ideas: [] };
}

Because other frames continue generating ideas normally, the overall brainstorming session survives a single bad LLM response.

Score Phase: Skip Ranking for Failed Ideas

Inside scoreIdeas (src/engine.ts, lines 107-112), a parsing failure results in an empty Map<string, Score>.

// src/engine.ts#L107-L112
try {
  const scores = parseJSON(raw, ScoreSchema);
  return new Map(Object.entries(scores));
} catch {
  return new Map<string, Score>();
}

With no scores applied, downstream ranking logic treats every idea as unscored and gracefully skips top-K selection for that batch rather than throwing.

Cluster Phase: Keep Ideas Unclustered

The clusterIdeas function (src/engine.ts, lines 52-56) falls back to an empty array of clusters.

// src/engine.ts#L52-L56
try {
  const clusters = parseJSON(raw, ClusterSchema);
  return clusters;
} catch {
  return [];
}

Ideas retain their default unclustered appearance, and the visualization or organization step proceeds without interruption.

Deepen Phase: Insert a Placeholder Sketch

In deepenIdea (src/engine.ts, lines 93-98), the engine returns a placeholder sketch and no child ideas.

// src/engine.ts#L93-L98
try {
  const parsed = parseJSON(raw, DeepenSchema);
  return parsed;
} catch {
  return {
    ideaId: idea.id,
    sketch: "(deepen pass failed to parse)",
    childIdeas: [],
  };
}

This prevents a hard crash while explicitly surfacing the failure to the user or logs.

How parseJSON in src/llm.ts Enables Safe Degradation

The helper at the center of this resilience is parseJSON, implemented in src/llm.ts. It acts as the single gateway through which all LLM outputs pass before entering the engine's business logic. By centralizing parsing in one utility and forcing each caller in src/engine.ts to handle exceptions locally, the repository keeps error handling explicit and phase-specific.

The frame definitions in src/frames.ts provide the cognitive contexts used during the diverge phase. When JSON parsing fails for one frame, that frame yields an empty idea list while neighboring frames continue unaffected.

The type definitions in src/types.ts ensure that every fallback object—whether an empty array, an empty Map, or a placeholder string—still satisfies the TypeScript contracts expected by the rest of the pipeline.

Summary

  • The ADHD engine uses a fail-open strategy: malformed LLM JSON degrades one phase but never kills the entire workflow.
  • src/engine.ts wraps every parseJSON call in try / catch, with five distinct fallbacks for reframe, diverge, score, cluster, and deepen.
  • Each fallback returns a valid, type-safe default so downstream phases continue processing.
  • src/llm.ts provides the core parseJSON helper, while src/types.ts guarantees fallback shapes match the expected contracts.

Frequently Asked Questions

Does the ADHD engine crash if the LLM returns invalid JSON?

No. When JSON parsing fails during ADHD phases, the engine catches the exception inside the calling function and returns a safe default. The pipeline remains alive, and subsequent phases continue executing with degraded but valid data.

Which phases are protected against JSON parsing errors?

All five major phases are protected: reframe, diverge, score, cluster, and deepen. Each has its own fallback logic defined in src/engine.ts, ensuring localized failure without cascading errors.

What is the parseJSON helper and where is it defined?

parseJSON is a utility defined in src/llm.ts that converts raw LLM text output into typed JavaScript objects. Every phase in src/engine.ts invokes this helper inside a try / catch block to standardize JSON parsing and error containment.

How does the engine maintain type safety when returning fallback values?

The fallbacks are designed to match the TypeScript interfaces declared in src/types.ts. Whether returning an empty array, an empty Map, or a placeholder string, each fallback satisfies the return type expected by the calling context, so the compiler and runtime both remain consistent.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →