Skip to content

fix(frontend): stop the evaluation run page minting an atom per read - #6196

Merged
mmabrouk merged 1 commit into
release/v0.113.0from
fix/r4-evaluation-run-update-loop
Aug 22, 2026
Merged

mmabrouk merged 1 commit into
release/v0.113.0from
fix/r4-evaluation-run-update-loop

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

Opening an evaluation run threw "Minified React error #185" and the error boundary replaced the page. React #185 is "Maximum update depth exceeded", so something was updating state without settling.

atomFamily in jotai keys its cache by reference unless you give it an areEqual. Version 2.20's implementation is a plain atoms.get(param) against a Map:

if (areEqual === undefined) {
  item = atoms.get(param)     // reference equality for an object key
}

Every family under EvalRunDetails is keyed by an object literal rebuilt at the call site. scenarioColumnValues.ts:485 is the load-bearing one:

const stepsQuery = get(scenarioStepsQueryFamily({scenarioId, runId}))

That literal is new on every evaluation, so the Map never hits and the family mints a brand-new atom each time. For the query families that is self-driving: each generation mounts its own QueryObserver, the observer emits, the emit invalidates the atom that created it, and the re-read mints the next generation. React counts an update per generation and eventually throws.

Why it never reproduced on an empty project

I could not reproduce this for a long time, and the reason is in the gate at scenarioSteps.ts:147:

enabled: Boolean(effectiveRunId && batcher && scenarioId)

No scenarios means no cells, which means nothing ever reads the family. A run also has to be non-terminal to be at its worst, because refetchInterval: runTerminal ? false : 5000 keeps every generation's observer emitting on a timer.

The measurement

I seeded a 25-scenario non-terminal run through the API and counted atom creations. About 200 cells should create about 200 atoms, once.

before    2.5s     486 column-value atoms,   1504 step-query atoms
          20s     3244 column-value atoms,  10744 step-query atoms, still climbing

after     2.5s     144 column-value atoms,     24 step-query atoms
          20s      144 column-value atoms,     24 step-query atoms, flat

Before, the counters grew by roughly 2,584 step-query atoms every five seconds, which is the refetch timer driving each generation. After, they reach their real size and stop.

Changes

Adds atoms/familyKeys.ts with sameFamilyKey, and passes it to the 24 object-keyed families across 9 files. The comparator compares the union of both key sets, so an absent key and an explicit undefined match, and it goes one level deeper for the column descriptor, whose pathSegments is an array of scalars.

Families keyed by a plain string are deliberately left alone. Giving those an areEqual makes jotai walk the whole Map linearly instead of hashing, so it would be a regression, not a fix. Three of them were caught and reverted while preparing this.

A note on where the bug came from

The atoms are byte-identical to v0.112.3. git diff v0.112.3..HEAD -- web/oss/src/components/EvalRunDetails is nine files of pure import-path and type swaps. This churn is older than the release.

What #6065 changed is the engine underneath: the antd table became the virtualized InfiniteVirtualTable, which re-renders cells far more often. That is what pushed a latent, bounded-looking churn past React's update-depth limit. Worth remembering when triaging the next "the extraction broke X": the extraction can be the trigger without being the author.

Tests

  • atoms/familyKeys.test.ts, 10 cases: the comparator's own behaviour, that an atomFamily given it returns the same atom for equal keys built fresh, that it still separates genuinely different keys, and one test that pins jotai's reference-keying so we find out if a future version makes these comparators redundant.
  • @agenta/oss suite: 45 files, 442 passed, 1 skipped.
  • tsc --noEmit clean for @agenta/oss and @agenta/ee.
  • pnpm lint-fix in web/: 25/25. Prettier 3.7.4 clean on all touched files.
  • One @agenta/oss production build.
  • Live on a v0.113.0 build from this branch: the run page renders its 19 virtualized rows, no error boundary, no update-depth error, and the network goes quiet once the queries settle.

What to QA

  • Open an evaluation run that has scenarios, on the Scenarios tab. It renders instead of showing "An Error Occurred", and stays up while you leave it open.
  • Do it on a run that is still executing. That is the worst case, because the steps query polls every five seconds; the page should stay stable rather than degrading.
  • Scroll the scenario table and open a scenario. Cell values, inputs, metrics and annotations all still resolve, since they read through the families this touches.
  • Switch between Overview, Scenarios and Configuration, then use Compare in the header.
  • Regression to watch: the families now dedupe by value rather than by reference, so the thing to check is that data still updates. Watch a running run and confirm the cells fill in as scenarios complete, rather than freezing on their first value.

Opening an evaluation run with real data threw React error #185, "Maximum
update depth exceeded", and the error boundary replaced the page.

jotai's atomFamily keys its cache by REFERENCE unless given an areEqual: 2.20's
implementation is a plain atoms.get(param) against a Map. Every family in
EvalRunDetails is keyed by an object literal rebuilt at the call site, e.g.
scenarioColumnValues.ts:485 does

    get(scenarioStepsQueryFamily({scenarioId, runId}))

so the Map never hits and each call mints a brand-new atom. For the query
families that is self-driving: each generation mounts its own observer, the
observer emits, the emit invalidates the atom that created it, and the re-read
mints the next generation. React counts an update per generation and throws.

Measured on a seeded 25-scenario non-terminal run, where ~200 cells should
create ~200 atoms once:

    before   2.5s   486 column-value atoms,  1504 step-query atoms
             20s   3244 column-value atoms, 10744 step-query atoms, climbing
    after    2.5s   144 column-value atoms,    24 step-query atoms
             20s    144 column-value atoms,    24 step-query atoms, flat

This is why it never reproduced on an empty project: the steps query is gated
on enabled: Boolean(effectiveRunId && batcher && scenarioId), so with no
scenarios nothing reads the family. A non-terminal run makes it worse, because
refetchInterval 5000 keeps every generation's observer emitting.

The atoms themselves are byte-identical to v0.112.3. The extraction (#6065)
replaced the antd table with the virtualized engine, which re-renders cells far
more, and that is what pushed a latent churn over React's update-depth limit.

Adds sameFamilyKey and passes it to the 24 object-keyed families across 9
files. Families keyed by a plain string are left alone: an areEqual makes jotai
walk the Map linearly instead of hashing, so it would be a regression there.
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Aug 22, 2026
@vercel

vercel Bot commented Aug 22, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 22, 2026 8:38am

Request Review

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Mahmoud Mabrouk seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability and performance when viewing evaluation run details.
    • Prevented duplicate internal state instances for equivalent query parameters.
    • Reduced the risk of excessive updates and related rendering errors.
    • Added coverage for matching, missing, nested, and differing parameter values.

Walkthrough

Changes

The change adds sameFamilyKey for value-based comparison of object keys and applies it to EvalRunDetails Jotai atom families. Tests cover comparison semantics and atom reuse.

Atom-family equality

Layer / File(s) Summary
Family-key comparator and tests
web/oss/src/components/EvalRunDetails/atoms/familyKeys.ts, web/oss/src/components/EvalRunDetails/atoms/familyKeys.test.ts
Adds sameFamilyKey with scalar, array, nested-record, and nullish-key comparison. Tests verify equality behavior and atom reuse.
EvalRunDetails atom-family integration
web/oss/src/components/EvalRunDetails/atoms/{annotations,invocationTraceSummary,metrics,scenarioSteps,scenarioTestcase,traces}.ts, web/oss/src/components/EvalRunDetails/atoms/table/testcases.ts
Applies sameFamilyKey to annotation, invocation, metric, scenario-step, scenario-testcase, testcase, and trace atom families.
Scenario column and selector integration
web/oss/src/components/EvalRunDetails/atoms/scenarioColumnValues.ts, web/oss/src/components/EvalRunDetails/hooks/useScenarioStepsSelectors.ts
Applies sameFamilyKey to scenario column value, selection, and selector atom families.

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

Merge Risk: 🟡 Moderate · up to 600ff

The change addresses the evaluation run update loop, but it also uses direct, unvalidated API requests at several data-loading boundaries, which can allow incompatible responses or parameter handling to reach the page; this should be corrected or explicitly accepted before merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 11 files.
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 frontend fix for excessive atom creation on the evaluation run page.
Description check ✅ Passed The description clearly explains the atomFamily cache bug, the fix, affected families, tests, and QA results.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/r4-evaluation-run-update-loop

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.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 582ff58c-bc63-48ca-b2d0-7df5fd566f30

📥 Commits

Reviewing files that changed from the base of the PR and between 5730de9 and 600ff6a.

📒 Files selected for processing (11)
  • web/oss/src/components/EvalRunDetails/atoms/annotations.ts
  • web/oss/src/components/EvalRunDetails/atoms/familyKeys.test.ts
  • web/oss/src/components/EvalRunDetails/atoms/familyKeys.ts
  • web/oss/src/components/EvalRunDetails/atoms/invocationTraceSummary.ts
  • web/oss/src/components/EvalRunDetails/atoms/metrics.ts
  • web/oss/src/components/EvalRunDetails/atoms/scenarioColumnValues.ts
  • web/oss/src/components/EvalRunDetails/atoms/scenarioSteps.ts
  • web/oss/src/components/EvalRunDetails/atoms/scenarioTestcase.ts
  • web/oss/src/components/EvalRunDetails/atoms/table/testcases.ts
  • web/oss/src/components/EvalRunDetails/atoms/traces.ts
  • web/oss/src/components/EvalRunDetails/hooks/useScenarioStepsSelectors.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.

@github-actions

github-actions Bot commented Aug 22, 2026 •

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-6196.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-6196-2630a79
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-08-22T10:43:46.104Z

@mmabrouk
mmabrouk marked this pull request as ready for review August 22, 2026 10:40
@dosubot dosubot Bot added the refactoring A code change that neither fixes a bug nor adds a feature label Aug 22, 2026
@mmabrouk
mmabrouk merged commit 454e653 into release/v0.113.0 Aug 22, 2026
66 of 68 checks passed

This branch was successfully deployed

1 active deployment
Preview — 600ff6a8 Deployed Aug 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug frontend refactoring A code change that neither fixes a bug nor adds a feature size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants