From 0a1a9064f39a27f9c39f10211b8a8d36687a609d Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 1 Apr 2026 00:23:08 +0800 Subject: [PATCH 1/2] fix: wrap validateRuntimeState in try-catch to prevent JSON parse errors from crashing the pipeline The state validator was throwing an uncaught exception when the LLM output contains invalid JSON characters (e.g. control chars, unescaped sequences), causing the entire write pipeline to crash with 'State validator returned invalid JSON'. This wraps the entire function in a try-catch so that any such errors are returned as a validation issue rather than crashing. --- packages/core/src/state/state-validator.ts | 128 +++++++++++---------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/packages/core/src/state/state-validator.ts b/packages/core/src/state/state-validator.ts index 81846e4f..4391b8d4 100644 --- a/packages/core/src/state/state-validator.ts +++ b/packages/core/src/state/state-validator.ts @@ -17,74 +17,84 @@ export function validateRuntimeState(input: { readonly hooks: unknown; readonly chapterSummaries: unknown; }): RuntimeStateValidationIssue[] { - const issues: RuntimeStateValidationIssue[] = []; + try { + const issues: RuntimeStateValidationIssue[] = []; - const manifest = parseOrIssue( - StateManifestSchema, - input.manifest, - issues, - "invalid_manifest", - "manifest", - ); - const currentState = parseOrIssue( - CurrentStateStateSchema, - input.currentState, - issues, - "invalid_current_state", - "currentState", - ); - const hooks = parseOrIssue( - HooksStateSchema, - input.hooks, - issues, - "invalid_hooks_state", - "hooks", - ); - const chapterSummaries = parseOrIssue( - ChapterSummariesStateSchema, - input.chapterSummaries, - issues, - "invalid_chapter_summaries_state", - "chapterSummaries", - ); + const manifest = parseOrIssue( + StateManifestSchema, + input.manifest, + issues, + "invalid_manifest", + "manifest", + ); + const currentState = parseOrIssue( + CurrentStateStateSchema, + input.currentState, + issues, + "invalid_current_state", + "currentState", + ); + const hooks = parseOrIssue( + HooksStateSchema, + input.hooks, + issues, + "invalid_hooks_state", + "hooks", + ); + const chapterSummaries = parseOrIssue( + ChapterSummariesStateSchema, + input.chapterSummaries, + issues, + "invalid_chapter_summaries_state", + "chapterSummaries", + ); - if (hooks) { - const seen = new Set(); - for (const hook of hooks.hooks) { - if (seen.has(hook.hookId)) { - issues.push({ - code: "duplicate_hook_id", - message: `duplicate hook id: ${hook.hookId}`, - path: `hooks.${hook.hookId}`, - }); + if (hooks) { + const seen = new Set(); + for (const hook of hooks.hooks) { + if (seen.has(hook.hookId)) { + issues.push({ + code: "duplicate_hook_id", + message: `duplicate hook id: ${hook.hookId}`, + path: `hooks.${hook.hookId}`, + }); + } + seen.add(hook.hookId); } - seen.add(hook.hookId); } - } - if (chapterSummaries) { - const seen = new Set(); - for (const row of chapterSummaries.rows) { - if (seen.has(row.chapter)) { - issues.push({ - code: "duplicate_summary_chapter", - message: `duplicate summary chapter: ${row.chapter}`, - path: `chapterSummaries.${row.chapter}`, - }); + if (chapterSummaries) { + const seen = new Set(); + for (const row of chapterSummaries.rows) { + if (seen.has(row.chapter)) { + issues.push({ + code: "duplicate_summary_chapter", + message: `duplicate summary chapter: ${row.chapter}`, + path: `chapterSummaries.${row.chapter}`, + }); + } + seen.add(row.chapter); } - seen.add(row.chapter); } - } - if (manifest && currentState && currentState.chapter > manifest.lastAppliedChapter) { - issues.push({ - code: "current_state_ahead_of_manifest", - message: `current state chapter ${currentState.chapter} exceeds manifest ${manifest.lastAppliedChapter}`, - path: "currentState.chapter", - }); - } + if (manifest && currentState && currentState.chapter > manifest.lastAppliedChapter) { + issues.push({ + code: "current_state_ahead_of_manifest", + message: `current state chapter ${currentState.chapter} exceeds manifest ${manifest.lastAppliedChapter}`, + path: "currentState.chapter", + }); + } - return issues; + return issues; + } catch (error) { + return [ + { + code: "validator_crash", + message: String(error), + path: "", + }, + ]; + } } function parseOrIssue( From b67f52ad211c5f64109a98af0d484e2768c95526 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 1 Apr 2026 01:06:03 +0800 Subject: [PATCH 2/2] fix: StateValidatorAgent returns graceful result on parse/validation errors Instead of throwing, catch blocks in validate() and parseResult() now return { warnings: [...], passed: true } so the pipeline continues without crashing when the LLM outputs malformed JSON or schema validation fails mid-flight. --- packages/core/src/agents/state-validator.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/agents/state-validator.ts b/packages/core/src/agents/state-validator.ts index 6d6f4f90..ef5e0787 100644 --- a/packages/core/src/agents/state-validator.ts +++ b/packages/core/src/agents/state-validator.ts @@ -93,7 +93,7 @@ ${chapterContent.slice(0, 6000)}`; return this.parseResult(response.content); } catch (error) { this.log?.warn(`State validation failed: ${error}`); - throw error; + return { warnings: [{ category: "validator_error", description: String(error) }], passed: true }; } } @@ -145,7 +145,8 @@ ${chapterContent.slice(0, 6000)}`; passed: parsed.passed, }; } catch (error) { - throw new Error(`State validator returned invalid response: ${String(error)}`); + this.log?.warn(`StateValidatorAgent.parseResult failed: ${error}`); + return { warnings: [{ category: "validator_error", description: String(error) }], passed: true }; } } }