Skip to content

Fix evals judge parser dropping YES/NO answers as UNKNOWN for Claude engine output - #49323

Merged
pelikhan merged 5 commits into
mainfrom
copilot/evals-daily-reports-2026-07-31
Jul 31, 2026
Merged

Fix evals judge parser dropping YES/NO answers as UNKNOWN for Claude engine output#49323
pelikhan merged 5 commits into
mainfrom
copilot/evals-daily-reports-2026-07-31

Conversation

Copilot AI commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

The Daily Evals Feature Report flagged the evals feature as DEGRADED: a 48.6% overall YES rate driven by 32 UNKNOWN answers spread across many unrelated workflows (Design Decision Gate, AI Moderator, Daily Assign Issue To User, etc.).

Tracing a real run (Design Decision Gate, run 30608805370) showed the judge model actually answered action-taken: YES and decision-justified: YES, but the pipeline recorded both as UNKNOWN.

Root cause

  • extractAssistantTextFromJsonlLog (actions/setup/js/run_evals.cjs) only decoded two JSONL shapes: the v3 turn_end event (message.content array) and the legacy assistant event (flat string content).
  • Claude engine's native stream-json format emits a top-level assistant event with a nested message.content array — a shape the function didn't recognize.
  • When undecoded, matching fell back to the raw JSON log text, where the literal \n escape sequence (backslash + n) sits directly before the next question ID. That letter n is a word character, so the \b word-boundary regex in extractAnswerByID fails to match — silently downgrading every question after the first to UNKNOWN.

Fix

  • Extend the JSONL decoding to treat assistant events with a message.content array the same as turn_end events, so text is extracted with real newlines before regex matching.
// before: only turn_end events were decoded via message.content
if (obj.type === "turn_end" && obj.message && Array.isArray(obj.message.content)) { ... }

// after: Claude's native "assistant" event uses the same nested shape
if ((obj.type === "turn_end" || obj.type === "assistant") && obj.message && Array.isArray(obj.message.content)) { ... }

Tests

  • Added a unit test for extractAssistantTextFromJsonlLog covering the Claude-native assistant/message.content shape.
  • Added an end-to-end parseMain regression test reproducing the exact real-world log (multi-question answer string with embedded \n), asserting all three answers resolve correctly instead of only the first.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 16.7 AIC · ⊞ 8.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 31, 2026 11:53
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Update daily evals feature report for 2026-07-31 Fix evals judge parser dropping YES/NO answers as UNKNOWN for Claude engine output Jul 31, 2026
Copilot AI requested a review from pelikhan July 31, 2026 11:56
@pelikhan
pelikhan marked this pull request as ready for review July 31, 2026 12:16
Copilot AI review requested due to automatic review settings July 31, 2026 12:16

Copilot AI left a comment

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.

🟢 Ready to approve

The focused parser change correctly addresses the reported JSONL shape and includes appropriate regression tests.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Fixes eval result parsing for Claude’s nested assistant JSONL events.

Changes:

  • Extracts text from Claude assistant.message.content arrays.
  • Adds unit and end-to-end regression coverage for multi-question answers.
File summaries
File Description
actions/setup/js/run_evals.cjs Supports Claude-native assistant events.
actions/setup/js/run_evals.test.cjs Verifies extraction and answer parsing.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #49323 does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Test Quality Sentinel: PR #49323 Analysis Complete. Score: 92/100 (Excellent). 2 new JS tests analyzed: 100% design tests (0% implementation). Regression tests verify Claude native assistant JSONL parsing with multiline answers. Edge-case coverage excellent. Inflation ratio 7:1 justified for surgical bug fix. APPROVED: 0% implementation tests (threshold 30%). Unable to post PR comment due to safeoutputs auth bridge issue.

@github-actions github-actions Bot left a comment

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.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

The fix correctly extends the v3 branch to also match Claude engine native assistant events. The two assistant branches remain mutually exclusive (one checks obj.message.content, the other obj.content as a plain string), so no double-extraction risk. Test coverage for the regression is solid.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 14.7 AIC · ⊞ 5.3K

@github-actions github-actions Bot left a comment

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.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — one minor suggestion on control flow; overall the fix is clean and well-tested.

📋 Key Themes & Highlights

Key Theme

  • Independent if branches: The two if blocks in extractAssistantTextFromJsonlLog are not else if, leaving a theoretical double-count path for an assistant event that has both message.content and a flat obj.content field. Flagged in the inline comment.

Positive Highlights

  • ✅ Root cause is precisely diagnosed and clearly described in the PR body
  • ✅ Minimal, surgical fix — only two changed lines in production code
  • ✅ Regression test directly reproduces the real-world failure (multi-question answer with embedded newlines), covering all three answer states (YES, NO, UNKNOWN)
  • ✅ Unit test for extractAssistantTextFromJsonlLog isolates the new code path clearly
  • ✅ Inline comment in the source accurately explains why both event types are handled identically

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 25.3 AIC · ⊞ 7K
Comment /matt to run again

// v1 legacy schema: assistant event carries raw text content directly
if (obj.type === "assistant" && typeof obj.content === "string" && obj.content) {
texts.push(obj.content);
}

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.

[/diagnosing-bugs] The two if branches are independent, not else if — an assistant event with both message.content array and a flat obj.content string would push text twice. In practice this shape doesn't appear, but using else if makes the mutual-exclusivity explicit and defends against unexpected future payloads.

💡 Suggested fix
if ((obj.type === "turn_end" || obj.type === "assistant") && obj.message && Array.isArray(obj.message.content)) {
  for (const part of obj.message.content) {
    if (part && typeof part.text === "string") {
      texts.push(part.text);
    }
  }
} else if (obj.type === "assistant" && typeof obj.content === "string" && obj.content) {
  texts.push(obj.content);
}

Using else if documents the intent that these two shapes are mutually exclusive and prevents double-counting if a future schema change produces an object with both fields.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in 7147347 — changed to else if so the two branches are mutually exclusive.

@github-actions github-actions Bot left a comment

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.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Verdict: approve with one non-blocking concern

Correct, well-tested fix for the Claude-native assistant event shape. One latent maintainability/correctness gap flagged inline.

Details

The widened condition on extractAssistantTextFromJsonlLog correctly recognizes Claude's native message.content array shape for assistant events. However, the new branch and the pre-existing legacy obj.content string branch are not mutually exclusive (both check obj.type === "assistant" independently), which could double-push text if a future/hybrid log shape carries both fields — flagged as a review comment with a suggested else if fix. Not blocking since no current known JSONL shape triggers it and test coverage for the new case is solid.

🔎 Code quality review by PR Code Quality Reviewer · auto · 25.8 AIC · ⊞ 7.8K
Comment /review to run again

Comment thread actions/setup/js/run_evals.cjs Outdated
}
}
// v1 legacy schema: assistant event carries raw text content
// v1 legacy schema: assistant event carries raw text content directly

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.

The two if blocks are independent (not else if), so an event with type === "assistant" and a nested message.content array could also match the legacy obj.content string check on the same object, pushing duplicate text.

💡 Why this matters

The widened condition on line 331 now treats assistant events the same as turn_end for the nested-array shape, but the pre-existing legacy branch below it (line 339) still independently checks obj.type === "assistant" regardless of whether the first branch already consumed it. Nothing guards against an object that happens to carry both a message.content array and a top-level content string — if any current or future Claude/engine payload variant includes both fields, the same logical answer text gets pushed twice into texts, which is then joined and searched by extractAnswerByID. Duplicate text is much harder to detect than the original UNKNOWN failure mode this PR fixes, since the regex would still match (just redundantly), masking future format-drift bugs instead of surfacing them.

Suggested fix: make the branches mutually exclusive, e.g.

if ((obj.type === "turn_end" || obj.type === "assistant") && obj.message && Array.isArray(obj.message.content)) {
  for (const part of obj.message.content) {
    if (part && typeof part.text === "string") texts.push(part.text);
  }
} else if (obj.type === "assistant" && typeof obj.content === "string" && obj.content) {
  texts.push(obj.content);
}

No current test in this PR exercises an object satisfying both conditions simultaneously, so this gap is untested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in 7147347 — changed to else if so the two branches are mutually exclusive, preventing double-extraction for any future payload carrying both fields.

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

Category: bug (evals judge parser fix)
Risk: medium
Priority: 78/100 (impact 35, urgency 25, quality 18)
Recommended action: fast_track

Rationale: Fixes a real production issue (Daily Evals Feature Report flagging false DEGRADED status due to YES/NO answers being misparsed as UNKNOWN across many unrelated workflows). Root-caused with a traced example run, includes a 49-line regression test, and has 2 APPROVED reviews already. Only concern: one check (push_experiments_state) reports failure, likely unrelated to this change but worth a quick look before merge.

Labels applied: pr-type:bug, pr-risk:medium, pr-priority:high, pr-action:fast_track, pr-agent:copilot.

Generated by 🔧 PR Triage Agent · auto · 38.1 AIC · ⌖ 7.66 AIC · ⊞ 7.9K ·

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@copilot please run the pr-finisher skill on this PR.

Please address any remaining review feedback, confirm the failing check is understood, and rerun the relevant checks once ready.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 16.7 AIC · ⊞ 8.1K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

[!WARNING]
threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding....

The review feedback has been addressed (commit 7147347): the two if branches in extractAssistantTextFromJsonlLog are now if/else if, making the assistant event paths mutually exclusive and preventing any double-extraction.

Regarding the push_experiments_state CI failure: this is the experiment-tracking infrastructure job that pushes state to a separate git branch. The failure is unrelated to the parser fix — it occurs on every PR run that goes through this workflow and cannot be reproduced locally. It needs a human to re-trigger CI after merge consideration.

Local validation summary:

  • make fmt
  • make lint ✅ (0 errors, pre-existing warnings only)
  • make test-unit ✅ (all tests pass)

@github-actions

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Thanks for the fix! 🎯

This PR looks excellent — the root cause analysis is thorough, the solution is surgical and well-targeted, and the test coverage is comprehensive. You have added both a regression test (parses multiple ID-based answers from Claude engine's native assistant JSONL event) that reproduces the exact real-world issue, and a focused unit test for the extractAssistantTextFromJsonlLog function covering the new Claude native shape.

The changes to run_evals.cjs cleanly extend the JSONL decoder to recognize Claude's native {"type":"assistant","message":{"content":[...]}} format alongside the existing v3 turn_end shape, fixing the word-boundary regex matching issue that was silently downgrading answers to UNKNOWN.

This is ready for merge!

Generated by ✅ Contribution Check · auto · 56.6 AIC · ⊞ 8.7K ·

Copilot AI requested a review from gh-aw-bot July 31, 2026 13:30
@pelikhan
pelikhan merged commit b0541d0 into main Jul 31, 2026
9 checks passed
@pelikhan
pelikhan deleted the copilot/evals-daily-reports-2026-07-31 branch July 31, 2026 13:33
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[evals] Daily Evals Feature Report - 2026-07-31

4 participants