Skip to content

[eb-v2 review] Generate/SDG: load-failure handling and coordination notes - #1650

Open
chiang-daniel wants to merge 1 commit into
review/eb-v2/basefrom
review/eb-v2/sdg
Open

[eb-v2 review] Generate/SDG: load-failure handling and coordination notes#1650
chiang-daniel wants to merge 1 commit into
review/eb-v2/basefrom
review/eb-v2/sdg

Conversation

@chiang-daniel

@chiang-daniel chiang-daniel commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review-only PR — do not merge. These changes are already landed on dchiang/eb-v2-merge (the eval-builder integration branch); this PR is a focused review surface for one area of that work. Every fix carries an inline comment explaining what was broken and why the fix takes this shape. Comments marked Personal review requested are the spots that want human judgment — the rest is context your tooling can skim. Reply on the inline comments (or add new line comments); accepted feedback will be implemented on eb-v2-merge and the commit sha posted back here. When review wraps, this PR is closed, not merged — its base and head are throwaway review refs.

Surface: the /generate landing route and its synth-data guidance model. One commit (landed as d8e558a) — surface a task-load failure on /generate instead of spinning forever, and correct a stale comment about which legacy config types carry eval_steps. About 80% of the diff is tests. One Personal review requested comment flags the failed-key retry behavior. This PR also carries three coordination notes as thread comments: a sync map for the overlap with #1630, the synthetic-user event contract the SDG stream consumes, and a shared note on the score-key hazard.

🤖 Generated with Claude Code


CI note: the "Check API Schema Bindings" failure here is an artifact of this review PR's pinned snapshot — it flagged an annotation file that has since been added on dchiang/eb-v2-merge (78081dd). No action needed from reviewers.

…ver; fix stale eval_steps comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The generate page now handles task-loading failures, ignores stale requests, renders normalized errors, and prevents retry loops. New tests cover loading, task rendering, failures, and recovery. The guidance comment covers both supported evaluation configuration types.

Changes

Generate task loading

Layer / File(s) Summary
Task loading error flow
app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/+page.svelte
handle_routing now catches current-route task-load failures, stores a KilnError, stops loading, and renders the error message.
Routing test coverage and intro stub
app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/page.test.ts, app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/__tests__/data_gen_intro_stub.svelte
Tests cover loading, single-turn and multi-turn rendering, failure handling, retry prevention, and navigation recovery. The stub exposes generation callbacks and setup state.
Evaluation step documentation
app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/synth_data_guidance_datamodel.ts
The judge_eval_steps comment covers both llm_as_judge and g_eval configurations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RouteNavigation
  participant handle_routing
  participant load_task
  participant GeneratePage
  RouteNavigation->>handle_routing: provide project and task route
  handle_routing->>load_task: load task
  load_task-->>handle_routing: task or failure
  handle_routing->>GeneratePage: update loading or load_error state
  GeneratePage-->>RouteNavigation: render task content or error message
Loading

Possibly related PRs

Suggested reviewers: sfierro

Poem

A rabbit found a loading trail,
And caught the task when routes would fail.
Stale hops now fade, retries rest,
Clear errors guide the page its best.
Tests thump softly: all is well.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the changes and review scope but omits the required Related Issues, Contributor License Agreement, and Checklists sections. Add the required template sections, provide related issue links or state none, confirm the CLA, and complete the test checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the affected areas and the main changes: task-load failure handling and coordination notes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch review/eb-v2/sdg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

// Show the error instead of spinning forever. The key stays marked as
// handled so we don't hot-loop retrying a failing load; navigating to a
// different key (or remounting the route) retries naturally.
load_error = createKilnError(e)

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.

The /generate routing awaited load_task with no catch and no error state, so any task-fetch failure left the page on a permanent silent spinner — a regression against main. This catches the failure into load_error and renders an error branch. See the flag below on the retry behavior.

loaded_task = await load_task(req_project_id, req_task_id)
} catch (e) {
if (req_project_id !== project_id || req_task_id !== task_id) return
// Show the error instead of spinning forever. The key stays marked as

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.

Personal review requested: On a load failure the routing key stays marked as handled, so a failing load isn't retried in place — recovery is by navigating to a different key or remounting the route. I chose this because un-marking the key made the reactive routing hot-loop retry the failing load. Confirm you're happy with navigation-based recovery here, versus an explicit Retry button that clears load_error and re-runs the routing for the same key.

<div class="w-full min-h-[50vh] flex justify-center items-center">
<div class="loading loading-spinner loading-lg"></div>
</div>
{:else if load_error}

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.

The error branch itself, matching the load-failure states used on the other data pages: a titled message with the underlying error text, shown in place of the spinner once load_error is set. This is what the user now sees instead of an indefinite spinner when the task can't be loaded.

private judge_eval_steps(): string[] {
// Only legacy llm_as_judge configs carry eval_steps; the typed properties
// union doesn't declare the key, so read it untyped.
// Both legacy config types (llm_as_judge and g_eval) carry eval_steps in

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.

The comment here claimed only llm_as_judge configs carry eval_steps, but g_eval configs carry it too — so a reader syncing against main could wrongly assume g_eval judges have no steps to rebuild and drop them. Corrected to name both legacy types; the untyped read is unchanged (the typed union still doesn't declare the key).

@chiang-daniel

Copy link
Copy Markdown
Contributor Author

#1630 overlap — sync map and an offer

Samantha — heads up on where #1630 touches the same files as the eval-builder fixes on this integration branch, so the merge is boring when it lands. None of these are asks; just a map.

When #1630 flows in:

  • deterministic_forms.test.ts, test_eval_model.py, test_eval_api.py: our changes rewrote several describes to be behavior-named; resolve those to our describes and take your copy updates on top. No logic conflict, just naming.
  • datamodel/eval.py: your Eval priority/status fields and our EvalInput tag validation + EvalRun docstring/properties-dispatch changes are in different regions — expect a clean merge; worth a glance that both validators still stand afterward.
  • eval_api.py: your creation/display paths and our score-summary addition are different endpoints — clean merge expected.
  • api_schema.d.ts: regenerate after the merge rather than hand-resolving it.

One concrete offer: we removed an orphaned output_value_expression binding in the judge builder (write-only, nothing read it). Your refactor relocates that binding into the new JudgeConfigFields component rather than dropping it. The cheapest fix is to just not carry it over in #1630 — happy to point at the exact removal if useful.

@chiang-daniel

Copy link
Copy Markdown
Contributor Author

Synthetic-user event contract the SDG stream consumes

Samantha — for the SDG stream, here's the current shape of the synthetic-user batch events so your consumer stays aligned (kiln_ai/synthetic_user/runner.py):

  • TurnCompletedEvent.turn_index is 1-based within the CURRENT drive attempt — a retried case restarts at 1, so read this field rather than counting events.
  • TurnCompletedEvent.su_next_message is str | None, and it is None on the case's final turn: the drive loop skips the synthetic-user call when no further target turn will consume it, so don't treat a null there as an error.
  • Cost is attempt-inclusive but split so you can add it up honestly: CaseCompletedEvent.total_cost is the surviving chain only, and CaseCompletedEvent.discarded_attempts_cost is the real provider spend of that case's earlier failed attempts (chains deleted, billing real) — add the two for what the case actually cost. CaseFailedEvent.total_cost is the spend across all of a failed case's attempts (nothing survives on disk). BatchCompletedEvent.total_cost already rolls all of that up.

If your stream currently derives turn numbers by counting or omits the discarded-attempt spend, those two are the ones to double-check.

@chiang-daniel

Copy link
Copy Markdown
Contributor Author

Score-key hazard — shared contract

Samantha — a shared gotcha worth stating once, since your starter-code fix and our result-badge fix both live on it: deterministic eval scores are keyed by the eval's spec-name json_keys, not a fixed literal like "match". Anything that reads a hardcoded score key (starter code, a result renderer, a fixture) works only against fabricated data and silently does nothing on real results — that's exactly how the Pass/Fail badge never rendered. The safe contract is to read the score values generically (they're uniform binaries) or resolve keys from the eval's declared output scores, never from a literal. Flagging so the fix in #1630's starter code and ours don't drift back to a hardcoded key.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/page.test.ts (1)

178-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a stale-rejection routing test.

Start a pending load for the first route. Navigate to a second route and complete its load. Then reject the first load. Assert that the second route still renders the intro and that no error state appears.

This test protects the stale-request guard in +page.svelte line 87.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/routes/`(app)/generate/[project_id]/[task_id]/page.test.ts
around lines 178 - 195, Add a test alongside the existing navigation retry case
that keeps the first mockLoadTask request pending, navigates to a second routing
key, resolves the second request, then rejects the original request. Assert the
second route still renders the data-gen intro stub and does not contain “Error
Loading Task,” covering the stale-request guard in the page component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/web_ui/src/routes/`(app)/generate/[project_id]/[task_id]/+page.svelte:
- Around line 221-229: Add role="alert" to the error-state container in the
load_error branch so screen readers announce the task-load failure when it
replaces the loading spinner. Keep the existing error message and layout
unchanged.

---

Nitpick comments:
In `@app/web_ui/src/routes/`(app)/generate/[project_id]/[task_id]/page.test.ts:
- Around line 178-195: Add a test alongside the existing navigation retry case
that keeps the first mockLoadTask request pending, navigates to a second routing
key, resolves the second request, then rejects the original request. Assert the
second route still renders the data-gen intro stub and does not contain “Error
Loading Task,” covering the stale-request guard in the page component.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 65116639-0a48-466e-b983-0b253e5fbe3e

📥 Commits

Reviewing files that changed from the base of the PR and between 790d770 and 430c7ad.

📒 Files selected for processing (4)
  • app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/+page.svelte
  • app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/__tests__/data_gen_intro_stub.svelte
  • app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/page.test.ts
  • app/web_ui/src/routes/(app)/generate/[project_id]/[task_id]/synth_data_guidance_datamodel.ts

Comment on lines +221 to +229
{:else if load_error}
<div
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
>
<div class="font-medium">Error Loading Task</div>
<div class="text-error text-sm">
{load_error.getMessage()}
</div>
</div>

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Announce the task-load failure.

load_error replaces the loading spinner asynchronously. A screen reader may not announce the new error state. Add role="alert" to the error container.

Proposed fix
-      <div
+      <div
+        role="alert"
         class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
       >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{:else if load_error}
<div
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
>
<div class="font-medium">Error Loading Task</div>
<div class="text-error text-sm">
{load_error.getMessage()}
</div>
</div>
{:else if load_error}
<div
role="alert"
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
>
<div class="font-medium">Error Loading Task</div>
<div class="text-error text-sm">
{load_error.getMessage()}
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/web_ui/src/routes/`(app)/generate/[project_id]/[task_id]/+page.svelte
around lines 221 - 229, Add role="alert" to the error-state container in the
load_error branch so screen readers announce the task-load failure when it
replaces the loading spinner. Keep the existing error message and layout
unchanged.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📊 Coverage Report

Overall Coverage: 93%

Diff: origin/review/eb-v2/base...HEAD

No lines with coverage information in this diff.


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.

2 participants