Skip to content

fix(frontend): key the evaluator-reference atom families by value - #6203

Closed
mmabrouk wants to merge 1 commit into
release/v0.113.0from
fix/evaluator-reference-atom-family-keys
Closed

mmabrouk wants to merge 1 commit into
release/v0.113.0from
fix/evaluator-reference-atom-family-keys

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

What

Give the two evaluator-reference atomFamilys in web/oss/src/components/References/atoms/entityReferences.ts an areEqual comparator, plus the three payload-keyed families in web/oss/src/state/queries/atoms/fetcher.ts.

Why

atomFamily keys its cache by reference unless it is given an areEqual — the lookup is a plain Map.get(param). Both families are keyed by an object literal that callers rebuild at every call site, so the Map never hits and each read mints a brand-new atom and a brand-new fetch.

The neighbouring families in the same file already declare comparators. workflowArtifactScopedQueryAtomFamily is read fifteen lines away inside the same function and is safe purely because of it — these two were the outliers, not the design.

evaluatorReferenceAtomFamily reads the query family from inside its own read function, so the miss also feeds back: each generation mounts its own query observer, the observer emits, and the emit invalidates the reader that created it.

What this is not

This is not the fix for the React error #185 on the /evaluations list page. I opened this investigation on that theory and the measurement disproved it. With the comparator removed, the atom count over 200 ticks is:

atoms: 1, 2, 2, 2, 2, 2, 2, 2, 2, 2    notifications: 1

It settles at two rather than running away, because this query carries staleTime: 5 * 60_000 and neither polls nor refetches on focus, so generation two reads settled cached data and emits nothing further. The family that did run away in #6196 polled while a run was non-terminal, and that is what compounded. Adding a refetchInterval here would turn this into that bug.

So the defect being fixed is a duplicated atom and a duplicated fetch per call site, and a latent loop. Real, worth fixing, not release-blocking.

Scope note

queriesQueryAtomFamily is included because evaluationRunsQueryOptionsAtom reads it from inside a derived atom on this very page, and is safe today only because that call site happens to pass hoisted module constants — one refactor away from breaking.

Tests

Two files, both red without the comparators and green with them:

  • evaluatorReferenceFamilyKeys.test.ts — asserts the rule: an equal-but-not-identical key returns the same atom, different keys still separate, and null and undefined normalise to one entry (useEvaluatorHeaderReference passes undefined, ReferenceLabels passes null; without normalising, one evaluator gets two cache entries and two fetches).
  • evaluatorReferenceLoop.test.ts — drives the real code path in a jotai store and counts the query atoms one subscribed evaluator column creates. Must be exactly one.

Verification run: pnpm --filter @agenta/oss exec vitest run — 47 files, 449 passed, 1 skipped.

Also checked live: brought this branch's frontend up against a real EE API with six seeded runs carrying evaluator references and mixed statuses, and the list page renders and resolves evaluator names correctly, with no console errors.

`atomFamily` keys its cache by REFERENCE unless it is given an `areEqual`, so an
object-literal key never hits the cache and every read mints a brand-new atom.
`evaluatorWorkflowQueryAtomFamily` and `evaluatorReferenceAtomFamily` are both keyed
by a literal that callers rebuild at each call site, and neither declared a
comparator. The neighbouring families in the same file already do, including
`workflowArtifactScopedQueryAtomFamily`, which is read fifteen lines away in the same
function and is safe purely because of it.

`evaluatorReferenceAtomFamily` reads the query family from inside its own read
function, so the miss also feeds back: each generation mounts its own query observer,
the observer emits, and the emit invalidates the reader that created it. Measured
trajectory over 200 ticks with the comparator removed:

    atoms: 1, 2, 2, 2, 2, 2, 2, 2, 2, 2   notifications: 1

It settles at two rather than running away, because this query carries a five minute
`staleTime` and neither polls nor refetches on focus, so generation two reads settled
cached data and emits nothing further. So this is a duplicated atom and a duplicated
fetch per call site, NOT the runaway that produced React error #185 on the run-details
page in #6196 — that family polled while a run was non-terminal, which is what
compounded. Adding a `refetchInterval` here would turn this into that bug.

The reference family's comparator also normalises `null` against `undefined`, because
`useEvaluatorHeaderReference` spells the absent half of the key one way and
`ReferenceLabels` the other, which otherwise gives one evaluator two cache entries and
two fetches.

The same omission in `queriesQueryAtomFamily` and its two selector families is fixed
alongside it. `evaluationRunsQueryOptionsAtom` reads the first one from inside a
derived atom on this very page, and is safe today only because that call site happens
to pass hoisted constants.

Two tests: one asserts the rule, that an equal-but-not-identical key returns the same
atom; the other drives the real code path in a jotai store and counts the atoms one
subscribed evaluator column creates. Both are red without the comparators.

This is hardening. It is NOT the fix for the #185 seen on the /evaluations list page,
which I could not reproduce and have not root-caused.
@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 9:46am

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

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cd4ac86-5fa3-4ea6-b237-bc48c3ed705c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@mmabrouk

Copy link
Copy Markdown
Member Author

Closing: the isolation test on the live R4b repro showed these comparators do not fix the evaluations-list loop on their own (the idempotent cache write in #6204 is necessary and sufficient). Kept out of v0.113.0 to keep the release change minimal; the comparator hardening can return as a follow-up on main.

@mmabrouk mmabrouk closed this Aug 22, 2026

This branch was successfully deployed

1 active deployment
Preview — 9ad2c01f 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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants