Skip to content

Instrument intermittently missing from the subject graph selector moments after its record is created #1474

Open

Description

@joshunrau

Summary

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.

Detail

Path: apps/web/src/routes/_app/datahub/$subjectId/graph.tsx → useInstrumentVisualization → useInstrumentInfoQuery (GET /v1/instruments/info).

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.

Activity

  1. added
    BugType: existing behavior is wrong
    Difficulty: HighMore than a day, or touches several workspaces
    Needs InfoCannot act until the reporter or a maintainer supplies more information
    on Jul 27, 2026
  2. added
    Priority: LowNice to have, cosmetic or speculative
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    Area: InfrastructureDeployment, Docker, CI, logging, CLIs, build and test tooling
    and removed
    Difficulty: HighMore than a day, or touches several workspaces
    on Sep 27, 2026
  3. joshunrau commented on Oct 26, 2026

    @joshunrau
    CollaboratorAuthor

    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.

    await page.route(/graph\.tsx\?tsr-split/, async (route) => {
      await new Promise((resolve) => setTimeout(resolve, 800));
      await route.continue();
    });

    Fix

    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.

  4. added
    Difficulty: LowIsolated change, about 2 hours or less
    and removed
    Needs InfoCannot act until the reporter or a maintainer supplies more information
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: InfrastructureDeployment, Docker, CI, logging, CLIs, build and test toolingBugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessPriority: LowNice to have, cosmetic or speculative

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions