From c562f4dc2a0f3cfb8eb34aa95d7d547220ca385f Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 12 May 2026 09:41:06 +0200 Subject: [PATCH] fix(coding-agent/eval): hardened eval parser against stray non-marker lines - Updated parseEvalInput to verify begin-cell markers before dereferencing regex matches. - Skipped stray non-marker lines between and after cells, preserving valid cells when model output is noisy. - Added eval parse regression tests for stray content and trailing chatter, and kept abort-line handling explicit. --- packages/agent/src/harmony-leak.ts | 2 +- packages/coding-agent/src/eval/parse.ts | 14 ++++++++- packages/coding-agent/test/eval/parse.test.ts | 31 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/harmony-leak.ts b/packages/agent/src/harmony-leak.ts index 4a85fda07..18cd5de5d 100644 --- a/packages/agent/src/harmony-leak.ts +++ b/packages/agent/src/harmony-leak.ts @@ -397,7 +397,7 @@ function truncateAtLineAndAppendSentinel( } function sha8(text: string): string { - return new Bun.CryptoHasher("sha256").update(text).digest("hex").slice(0, 8); + return Bun.sha(text, "hex").slice(0, 8); } const PREVIEW_KEEP_RE = new RegExp(`[${SCRIPT_CLASS}\\s】【”“…」「、。]`, "u"); diff --git a/packages/coding-agent/src/eval/parse.ts b/packages/coding-agent/src/eval/parse.ts index fc1ac6fcd..d6c44f8e6 100644 --- a/packages/coding-agent/src/eval/parse.ts +++ b/packages/coding-agent/src/eval/parse.ts @@ -148,7 +148,19 @@ export function parseEvalInput(input: string): ParsedEvalInput { } while (i < lines.length) { - const beginMatch = BEGIN_RE.exec(lines[i])!; + const beginMatch = BEGIN_RE.exec(lines[i]); + if (!beginMatch) { + // Stray content between/after cells (blank lines were already + // consumed). `*** Abort` here terminates parsing; anything else + // — typically a harmony-leak fragment or model garbage — is + // skipped rather than crashing the parser. + if (ABORT_RE.test(lines[i])) { + aborted = true; + break; + } + i++; + continue; + } const langToken = beginMatch[1]; const explicitLanguage = resolveLang(langToken); i++; diff --git a/packages/coding-agent/test/eval/parse.test.ts b/packages/coding-agent/test/eval/parse.test.ts index 48f77a7fb..ffeca3343 100644 --- a/packages/coding-agent/test/eval/parse.test.ts +++ b/packages/coding-agent/test/eval/parse.test.ts @@ -270,4 +270,35 @@ print(1) expect(result.aborted).toBeUndefined(); }); }); + + it("does not crash on stray non-marker lines between cells", () => { + // Regression: prior to fix, the outer parse loop unconditionally ran + // `BEGIN_RE.exec(lines[i])!` on whatever followed a closed cell, so a + // stray line — common in harmony-leak fragments — threw + // "null is not an object (evaluating 'BEGIN_RE.exec(lines[i])[1]')". + const result = parseEvalInput(`*** Begin PY +print("a") +*** End PY +stray junk that is not a marker +*** Begin PY +print("b") +*** End PY +`); + expect(result.aborted).toBeUndefined(); + expect(result.cells).toHaveLength(2); + expect(result.cells[0].code).toBe('print("a")'); + expect(result.cells[1].code).toBe('print("b")'); + }); + + it("does not crash on trailing stray content after the final cell", () => { + const result = parseEvalInput(`*** Begin PY +print(1) +*** End PY +leftover model chatter +more junk +`); + expect(result.aborted).toBeUndefined(); + expect(result.cells).toHaveLength(1); + expect(result.cells[0].code).toBe("print(1)"); + }); });