You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Instrument intermittently missing from the subject graph selector moments after its record is created #1474
Intermittent, not reliably reproducible. Navigating straight from completing an instrument to that subject's Graph tab sometimes leaves the instrument missing from the Graph tab's instrument selector, even though the record exists.
Observed in roughly 1 of every 8 full Playwright suite runs while developing e2e coverage. At the exact same point the instrument is reliably present when checked via the Table tab instead, whose data-fetch path looks equivalent.
Static reading turned up no obvious cause on either side:
Client: staleTime: 0, and switching tabs fully remounts the route.
Server: apps/api/src/instruments/instruments.service.ts#find, filtering on records.some.subjectId.
A page.reload() sometimes recovered it and sometimes did not, which reads more like a write-visibility race between the record write and this specific read than a client-side caching problem.
Status
Unconfirmed — filed so it is not lost. Worth a look with real network tracing if it recurs; the MongoDB read-concern / replica-set visibility angle seems the most likely place to start.
Workaround in the test suite
SubjectGraphPage.selectInstrument (testing/src/pages/_app/datahub/$subjectId/graph.page.ts, branch e2e-tests) uses a bounded retry-and-reload loop rather than asserting the race away.
The retry-and-reload loop is still on main (d86740e), at testing/src/pages/_app/datahub/subjects/$subjectId/graph.page.ts:33-50. The API explanation does not fit, though. The likely cause is a race between the page object and the lazily loaded graph route.
Why not the API
The records.some filter is gone. 11edc3f (#1423, merged 2026-07-30) replaced it with findInstrumentIdsBySubject (apps/api/src/instruments/instruments.service.ts:632-646), and the commits that hit the flake (1f8b01b, 0c29ffe) predate that merge. Neither version of the query leaves room for a read gap:
dev:test sets NODE_ENV=test, so the API runs on a one-member in-memory replica set (apps/api/src/core/prisma.ts:197). There is no secondary to lag behind.
The renderer awaits POST /v1/instrument-records before it shows the summary (packages/react-core/src/components/InstrumentRenderer/ScalarInstrumentRenderer.tsx:82-88).
The Table tab runs the same record lookup earlier. kind: 'FORM' is only applied afterwards, in memory (instruments.service.ts:312-315).
Likely cause
autoCodeSplitting: true (apps/web/vite.config.ts:46) puts the graph component in a lazy chunk. TanStack Router pushes the URL first, then keeps the table rendered until that chunk loads or defaultPendingMs (1000 ms; apps/web/src/router.tsx does not set it) runs out. So the tab does remount, but only once the chunk arrives.
waitForURL('**/graph') (testing/src/specs/subject-detail.spec.ts:65) resolves on the URL alone. instrumentSelectTrigger (graph.page.ts:12) is an unscoped getByTestId('select-instrument-dropdown-trigger'), and the table has a trigger with the same id because both tabs use SelectInstrument (apps/web/src/components/SelectInstrument.tsx:17). So the click can open the table's dropdown. When the graph mounts, that dropdown unmounts with the table and the graph's own Select stays closed. The option then looks missing, or detaches mid-click. A reload lands on /graph directly, with no table to hit.
No other spec opens /graph, and Vite does not pre-transform dynamic imports. The first copy of this test in a run therefore also waits for Vite to transform the chunk, so how long the race window stays open changes from run to run.
The loop also hides a failure it cannot recover from. If the table's option is clicked before the graph mounts, selectInstrument returns success. selectMeasure then waits on a Measures button that stays disabled (apps/web/src/routes/_app/datahub/subjects/$subjectId/graph.tsx:133) until the test times out.
Confirming it
Before the Graph tab click, add the following (not to be committed), then call selectInstrument(INSTRUMENT_TITLE, 1). If this is the cause, the test fails on every run.
The fix stays in testing/, which has no unit tier:
graph.page.ts: scope the trigger to page.getByTestId('instrument-select-dropdown-container'), which only graph.tsx:107 renders. Replace the loop with a trigger click followed by an option click, since auto-wait covers the info query. Delete the comment at :26-32.
subject-detail.spec.ts:58-59: the test.slow() comment cites selectInstrument retrying. Trim it to the seeding cost.
Verify: the delayed-chunk check above should now pass. Then run pnpm --filter @opendatacapture/testing test:e2e subject-detail.spec.ts --repeat-each=20, which repeats the graph test on both chromium and firefox.
#1807 changes subject-detail.spec.ts:65 but keeps it a URL-only wait. It does not touch graph.page.ts, SelectInstrument or the route splitting, and it keeps the instrument-select-dropdown-container wrapper. So it does not fix this, and this fix can land before or after it. Since the fix is in the test suite, Area: API no longer applies, and Needs Info can become Difficulty: Low.
Triage note written with Claude Code. Code references point at main @ d86740e.
Activity
joshunrau commentedon Oct 26, 2026
The retry-and-reload loop is still on
main(d86740e), attesting/src/pages/_app/datahub/subjects/$subjectId/graph.page.ts:33-50. The API explanation does not fit, though. The likely cause is a race between the page object and the lazily loaded graph route.Why not the API
The
records.somefilter is gone. 11edc3f (#1423, merged 2026-07-30) replaced it withfindInstrumentIdsBySubject(apps/api/src/instruments/instruments.service.ts:632-646), and the commits that hit the flake (1f8b01b, 0c29ffe) predate that merge. Neither version of the query leaves room for a read gap:dev:testsetsNODE_ENV=test, so the API runs on a one-member in-memory replica set (apps/api/src/core/prisma.ts:197). There is no secondary to lag behind.POST /v1/instrument-recordsbefore it shows the summary (packages/react-core/src/components/InstrumentRenderer/ScalarInstrumentRenderer.tsx:82-88).kind: 'FORM'is only applied afterwards, in memory (instruments.service.ts:312-315).Likely cause
autoCodeSplitting: true(apps/web/vite.config.ts:46) puts the graph component in a lazy chunk. TanStack Router pushes the URL first, then keeps the table rendered until that chunk loads ordefaultPendingMs(1000 ms;apps/web/src/router.tsxdoes not set it) runs out. So the tab does remount, but only once the chunk arrives.waitForURL('**/graph')(testing/src/specs/subject-detail.spec.ts:65) resolves on the URL alone.instrumentSelectTrigger(graph.page.ts:12) is an unscopedgetByTestId('select-instrument-dropdown-trigger'), and the table has a trigger with the same id because both tabs useSelectInstrument(apps/web/src/components/SelectInstrument.tsx:17). So the click can open the table's dropdown. When the graph mounts, that dropdown unmounts with the table and the graph's own Select stays closed. The option then looks missing, or detaches mid-click. A reload lands on/graphdirectly, with no table to hit./graph, and Vite does not pre-transform dynamic imports. The first copy of this test in a run therefore also waits for Vite to transform the chunk, so how long the race window stays open changes from run to run.selectInstrumentreturns success.selectMeasurethen waits on aMeasuresbutton that stays disabled (apps/web/src/routes/_app/datahub/subjects/$subjectId/graph.tsx:133) until the test times out.Confirming it
Before the Graph tab click, add the following (not to be committed), then call
selectInstrument(INSTRUMENT_TITLE, 1). If this is the cause, the test fails on every run.Fix
The fix stays in
testing/, which has no unit tier:graph.page.ts: scope the trigger topage.getByTestId('instrument-select-dropdown-container'), which onlygraph.tsx:107renders. Replace the loop with a trigger click followed by an option click, since auto-wait covers the info query. Delete the comment at:26-32.subject-detail.spec.ts:58-59: thetest.slow()comment citesselectInstrumentretrying. Trim it to the seeding cost.pnpm --filter @opendatacapture/testing test:e2e subject-detail.spec.ts --repeat-each=20, which repeats the graph test on both chromium and firefox.#1807 changes
subject-detail.spec.ts:65but keeps it a URL-only wait. It does not touchgraph.page.ts,SelectInstrumentor the route splitting, and it keeps theinstrument-select-dropdown-containerwrapper. So it does not fix this, and this fix can land before or after it. Since the fix is in the test suite,Area: APIno longer applies, andNeeds Infocan becomeDifficulty: Low.Triage note written with Claude Code. Code references point at
main@d86740e.