Skip to content

fix(executor): harden JSON parsing and error visibility in block handlers#4788

Open
rpatricksmith wants to merge 2 commits into
simstudioai:mainfrom
rpatricksmith:fix/executor-json-parsing
Open

fix(executor): harden JSON parsing and error visibility in block handlers#4788
rpatricksmith wants to merge 2 commits into
simstudioai:mainfrom
rpatricksmith:fix/executor-json-parsing

Conversation

@rpatricksmith
Copy link
Copy Markdown

Summary

Replace bare JSON.parse() calls in executor block handlers with the project's own parseJSON/parseJSONOrThrow utilities from executor/utils/json.ts, and add logging to empty catch blocks that silently swallow errors.

The Problem

Five executor handler files use bare JSON.parse() instead of the project's own parsing utilities — parseJSON(value, fallback) and parseJSONOrThrow(value) in executor/utils/json.ts. These utilities already handle trimming, fallback values, and enriched error messages. The handlers bypass them, creating inconsistency and missing out on the error enrichment.

Additionally, the router handler has 2 empty catch (_e) {} blocks (lines 121, 270) that silently swallow provider API error response parsing failures. When a provider returns a non-JSON error page (e.g., HTML 502), the error vanishes — making debugging production failures harder.

The utilities are already imported by some of these files (for parseObjectStrings). The import path exists — handlers just drifted from using it for JSON parsing.

The Fix

Handler changes (5 files):

  • response-handler.tsJSON.parse(inputs.data)parseJSON(inputs.data, inputs.data) (fallback to original string)
  • human-in-the-loop-handler.ts — identical change to response-handler (duplicated parseResponseData method)
  • router-handler.ts — empty catches → logger.warn(...), JSON.parse(result.content)parseJSONOrThrow(result.content), parseRoutesparseJSON(input, [])
  • api-handler.tsJSON.parse(trimmedBody) + empty catch → parseJSON(trimmedBody, processedInputs.body)
  • generic-handler.tsJSON.parse(value.trim())parseJSON(value, value)

What's NOT changed:

  • The catch-all error blocks in response-handler and HITL handler that intentionally return error objects instead of throwing — changing those would alter execution semantics
  • any types in handler signatures — separate concern

Test additions (5 files, 2 new):

  • New response-handler.test.ts — canHandle, structured data, JSON data, malformed JSON fallback, catch-all error path
  • New human-in-the-loop-handler.test.ts — canHandle, human operation execution, malformed JSON, catch-all error path
  • router-handler.test.ts — legacy/V2 non-JSON error response handling, invalid JSON routes
  • api-handler.test.ts — malformed JSON body keeps original string
  • generic-handler.test.ts — malformed JSON field keeps original string

Testing

171 handler tests pass. Lint clean. No regressions. 3 pre-existing PostCSS config failures in unrelated handler suites (agent-handler, function-handler, workflow-handler).


Found while analyzing the codebase with Anatomia. Full analysis →

…lers

Replace bare JSON.parse() calls across 5 executor handler files with
the project's own parseJSON/parseJSONOrThrow utilities from
executor/utils/json.ts. Add logger.warn to 2 empty catch blocks in
the router handler that previously swallowed errors silently.

The utilities already exist and are imported by some of these files
(for parseObjectStrings). This makes handlers consistent with the
project's established parsing patterns.

Also adds test coverage for error paths: 2 new test files
(response-handler, human-in-the-loop-handler) and error-path test
cases for 3 existing test files (router, api, generic handlers).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 29, 2026 7:24am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 29, 2026

PR Summary

Low Risk
Refactoring with preserved fallbacks and added tests; router invalid routes still surface as empty route lists rather than parse throws.

Overview
Executor block handlers now route user and provider JSON through shared parseJSON / parseJSONOrThrow helpers instead of ad-hoc JSON.parse and silent catch blocks.

API, generic, response, and human-in-the-loop handlers use parseJSON with explicit fallbacks (e.g. keep the original string for malformed JSON bodies or json-typed inputs). Router (legacy and V2) logs when provider error bodies are not JSON, parses V2 LLM output with parseJSONOrThrow, and normalizes routes via parseJSON with validation when the result is not an array.

Tests cover malformed JSON fallbacks, non-JSON provider errors, invalid route strings, and new suites for response and human-in-the-loop handlers.

Reviewed by Cursor Bugbot for commit 7308b53. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR replaces bare JSON.parse() calls across five executor handler files with the project's parseJSON/parseJSONOrThrow utilities, and adds logger.warn to two previously-empty catch blocks in router-handler.ts that were silently discarding provider error bodies.

  • Router handler: empty catches now log via logger.warn; parseJSONOrThrow used for V2 route-selection response; parseRoutes refactored to use parseJSON — but the new implementation's logger.error guard is unreachable for invalid-JSON inputs (see inline comment).
  • Response, HITL, generic, and API handlers: bare JSON.parse replaced with parseJSON(value, fallback), preserving the original string on failure; however three of these handlers previously had explicit logger.warn calls in their catch blocks, which are silently dropped by this change.
  • Tests: two new test files added (response-handler.test.ts, human-in-the-loop-handler.test.ts) and three existing suites extended, covering malformed-JSON fallback paths.

Confidence Score: 4/5

Safe to merge; all changed handlers preserve correct fallback behaviour and the router's previously-silent error-body catches now log properly.

The functional behaviour of every handler is preserved — invalid JSON still falls back to the original value or an empty array, and execution continues correctly. The two issues flagged are purely observability regressions: parseRoutes now silently swallows invalid-JSON route strings (the logger.error guard is unreachable for that case), and three handlers lose their pre-existing logger.warn calls on JSON parse failure. Neither affects runtime correctness, but both make diagnosing misconfigured route inputs or malformed JSON payloads harder in production.

router-handler.ts deserves a second look on the parseRoutes method — the logger.error branch is only reachable when JSON is valid but not an array, not when it is outright invalid.

Important Files Changed

Filename Overview
apps/sim/executor/handlers/router/router-handler.ts Empty catches replaced with logger.warn; parseJSONOrThrow used for V2 route response; parseRoutes refactored but the new implementation silently swallows invalid-JSON parse errors (the logger.error guard is unreachable for that case)
apps/sim/executor/handlers/generic/generic-handler.ts JSON field parsing now delegates to parseJSON utility; functional behaviour preserved but the previous per-field logger.warn on parse failure is silently dropped
apps/sim/executor/handlers/response/response-handler.ts Bare JSON.parse replaced with parseJSON utility; fallback behaviour identical but the pre-existing logger.warn on parse failure is removed
apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.ts Identical parseResponseData refactor as response-handler; else-if flattened to if; same logger.warn regression applies
apps/sim/executor/handlers/api/api-handler.ts Empty catch removed; parseJSON now explicitly returns original string as fallback — clean improvement with no regressions
apps/sim/executor/handlers/router/router-handler.test.ts Adds tests for non-JSON provider error responses (legacy + V2) and invalid route JSON; coverage is accurate but does not assert that errors are logged
apps/sim/executor/handlers/response/response-handler.test.ts New test file; covers canHandle, structured/json/object data modes, malformed JSON fallback, and catch-all error path
apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.test.ts New test file; covers canHandle, human-operation shape, malformed-JSON api mode, valid JSON parse, error response, and resume-link inclusion
apps/sim/executor/handlers/generic/generic-handler.test.ts Adds malformed-json-field test; verifies that the original string passes through as-is to executeTool
apps/sim/executor/handlers/api/api-handler.test.ts Adds malformed-JSON-body test confirming the original string is forwarded unchanged; clean addition

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Block Handler receives input] --> B{Handler type?}
    B -->|API / Generic / Response / HITL| C[parseJSON value, fallback]
    C --> D{Valid JSON?}
    D -->|Yes| E[Return parsed object]
    D -->|No| F[Return fallback string — silent, no log]
    B -->|Router V2 — route selection| G[parseJSONOrThrow result.content]
    G --> H{Valid JSON?}
    H -->|Yes| I[Extract route + reasoning]
    H -->|No| J[Catch _parseError → log error → use raw content]
    B -->|Router — error response| K[response.json try/catch]
    K -->|Success| L[Use errorData.error message]
    K -->|Failure| M[logger.warn — was empty catch before]
    B -->|Router V2 — parseRoutes| N[parseJSON input, empty-array fallback]
    N --> O{!Array.isArray parsed?}
    O -->|true — valid but wrong type| P[logger.error + return empty array]
    O -->|false — invalid JSON returns fallback| Q[Return empty array — silent, no log]
Loading

Reviews (1): Last reviewed commit: "fix(executor): harden JSON parsing and e..." | Re-trigger Greptile

Comment on lines 374 to 384
private parseRoutes(input: any): RouteDefinition[] {
try {
if (typeof input === 'string') {
return JSON.parse(input)
}
if (Array.isArray(input)) {
return input
}
return []
} catch (error) {
logger.error('Failed to parse routes:', { input, error })
if (Array.isArray(input)) {
return input
}
const parsed = parseJSON(input, [])
if (!Array.isArray(parsed)) {
logger.error('Failed to parse routes:', { input })
return []
}
return parsed
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The !Array.isArray(parsed) guard never fires for invalid JSON inputs. When input is a malformed JSON string, parseJSON(input, []) silently returns the [] fallback, which passes the Array.isArray check — so logger.error is dead code for the most common failure case (parse errors). The old implementation explicitly caught SyntaxError and logged both the input and the error. Consider restoring a try/catch for this method so that both invalid JSON and unexpected-type cases are logged.

Suggested change
private parseRoutes(input: any): RouteDefinition[] {
try {
if (typeof input === 'string') {
return JSON.parse(input)
}
if (Array.isArray(input)) {
return input
}
return []
} catch (error) {
logger.error('Failed to parse routes:', { input, error })
if (Array.isArray(input)) {
return input
}
const parsed = parseJSON(input, [])
if (!Array.isArray(parsed)) {
logger.error('Failed to parse routes:', { input })
return []
}
return parsed
}
private parseRoutes(input: any): RouteDefinition[] {
if (Array.isArray(input)) {
return input
}
if (typeof input === 'string') {
try {
const parsed = JSON.parse(input.trim())
if (!Array.isArray(parsed)) {
logger.error('Failed to parse routes: expected array', { input })
return []
}
return parsed
} catch (error) {
logger.error('Failed to parse routes:', { input, error })
return []
}
}
return []
}

Comment on lines 47 to 49
if (inputType === 'json' || inputType === 'array') {
try {
finalInputs[key] = JSON.parse(value.trim())
} catch (error) {
logger.warn(`Failed to parse ${inputType} field "${key}":`, {
error: toError(error).message,
})
}
finalInputs[key] = parseJSON(value, value)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Warn log removed for malformed JSON fields. The previous try/catch logged logger.warn(\Failed to parse ${inputType} field "${key}":`, { error })on parse failure. Replacing it withparseJSON(value, value)— which silently returns the fallback — drops that diagnostic. The same pattern applies inresponse-handler.tsandhuman-in-the-loop-handler.ts, both of which also had logger.warn('Failed to parse JSON data, returning as string:', error)` before. With these warnings gone, malformed-JSON inputs in production are now invisible in logs until a downstream symptom appears.

parseJSON(input, []) returns [] on parse failure, making the
subsequent !Array.isArray check unreachable for the invalid-JSON case.
Use null as the fallback to distinguish parse failure from valid-but-
not-array, and log both cases separately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant