Skip to content

Flaky test: recording integration.basic.test.ts fire-and-forget dispose() races temp-dir cleanup (ENOENT between tests) #3854

Open

Description

@acoliver

packages/core/src/recording/integration.basic.test.ts sometimes fails under full-suite load on macOS with an unhandled error. The failure is attributed to 12: delete session removes the file:

# Unhandled error between tests
ENOENT: no such file or directory, open '.../integration-test-XXXX/chats/session-...jsonl'
  at writeBatchToFile (packages/core/src/recording/SessionRecordingService.ts:526)
  at drain (SessionRecordingService.ts:1061)
  at flush (SessionRecordingService.ts:131)
  at dispose (SessionRecordingService.ts:680)

The file passes alone (3/3 runs). Several tests in the file end with void result.recording.dispose();, which does not wait for the final flush. When the next test or afterEach removes the temp chats directory first, the pending flush writes into a missing directory and Bun reports it as an unhandled error between tests.

Suggested fix: await the dispose() calls (or join them in afterEach) so each recording finishes flushing before its directory is removed. Seen while verifying PR #3842, which does not modify this test file.

Activity

  1. github-actions commented on Oct 11, 2026

    @github-actions
    Contributor

    LLxprt Issue Planner — infrastructure failure

    The autonomous planner failed with exit code 1. Please inspect the workflow logs (Run planner agent section) and re-run once resolved.

  2. acoliver commented on Oct 14, 2026

    @acoliver
    CollaboratorAuthor

    Same race in the sibling file packages/core/src/recording/integration.advanced.test.ts. Under full-suite load on macOS, 19: (property) seq numbers monotonic after N resumes fails with Property failed after 1 tests (counterexample [4]). Underneath is the same unhandled ENOENT from writeBatchToFile → drain → flush → dispose (SessionRecordingService.ts:526/499/578/680). The flush writes into a chats/ directory that is already gone.

    Run alone, the file passes 20/20 three times. The fix is the same as for integration.basic: await (or join in afterEach) every recording dispose() before the temp directory is removed.

  3. acoliver commented on Oct 16, 2026

    @acoliver
    CollaboratorAuthor

    This recurred on the #2616 integration branch issue2616-final at 9b62161 (macOS arm64, Bun 1.3.14).

    In the full npm run test --workspace packages/core run, 500 of 501 files passed. The failure was attributed to 12: delete session removes the file, with the same unhandled ENOENT:

    ENOENT: no such file or directory, open '.../integration-test-XXXX/chats/session-...'
      at async writeBatchToFile (SessionRecordingService.ts:526)
      at async drain (SessionRecordingService.ts:499)
      at async flush (SessionRecordingService.ts:578)
      at async dispose (SessionRecordingService.ts:680)
    

    I then ran bun test ./src/recording/integration.basic.test.ts alone three times: 15 pass / 1 fail, then 16 / 0, then 16 / 0. So it also fails occasionally with no other suite running, not only under full-suite load.

    packages/core/src/recording/ has no changes between the previously verified head and this one, where the same file passed. The suggested fix still applies: await each recording.dispose() instead of void-ing it, so the final flush completes before the test's temp directory is removed.

  4. acoliver commented on Oct 16, 2026

    @acoliver
    CollaboratorAuthor

    Further evidence from 2026-10-16. The race now also fails when the file runs alone, not only under full-suite load.

    packages/core/src/recording/integration.basic.test.ts run alone (bun test --timeout 60000 ./src/recording/integration.basic.test.ts) at a machine load average of about 90 to 105 failed 2 of 5 runs. Each failing run reported 15 pass, 1 fail, 1 error; the other 3 runs passed 16 of 16.

    The failing case is always "12: delete session removes the file". It fails at line 520 before any deletion happens:

    520 |     await expect(fs.access(filePath)).resolves.toBeFalsy();
    Expected promise that resolves
    Received promise that rejected
    

    Before it, Bun reports # Unhandled error between tests with a stack through SessionRecordingService.dispose -> flush -> drain -> writeBatchToFile. That comes from the void result.recording.dispose() at the end of the previous case ("11: resume specific session by ID"). The unawaited flush is still running when case 12 starts. Case 12's freshly recorded file is then missing when it checks for it.

    The same file also failed in two full core-suite runs on the #2616 integration branch: integration.advanced (20 of 20 pass plus 1 error) and integration.basic. packages/core has no changes between the commits involved. Awaiting every recording.dispose() in these files, or joining them in afterEach before the temp directory is removed, should make both cases deterministic.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions