Skip to content

CI: two flake sources produce reds that look like code defects (pnpm setup race, post-teardown 57P01) #125

Description

@KenTaniguchi-R

Two distinct flake sources hit four separate runs today. Neither is a code defect, and both have narrow fixes. Filing with evidence so the next red is triaged in seconds rather than by bisecting a diff.

1. pnpm/action-setup races on a shared install dir

Error: ENOTEMPTY: directory not empty, rmdir
  '/Users/taniguchiryusei/setup-pnpm/node_modules/.bin/store/v11/files/03'

The action installs into ~/setup-pnpm, which is shared across every job on the self-hosted runner. Two jobs from different refs starting close together race on it, and the loser fails before a single test runs.

The workflow's concurrency group is keyed on github.ref, so it correctly serialises runs for one branch but does nothing across branches — which is exactly the case that collides.

Seen on: run 33297952151 (PR #124). A re-run with no code change passed.

Fix options, cheapest first:

  • standalone: true on pnpm/action-setup — installs a self-contained binary, skipping the node_modules layout that races.
  • or dest: ~/setup-pnpm-${{ github.run_id }} — unique per run, at the cost of leaving directories behind.

2. A post-teardown 57P01 fails a run where every test passed

Run 33297710143 (main @ 97a3c23):

Test Files  120 passed (120)
Tests       865 passed (865)
Errors      1 error
→ exit code 1

The error is a Postgres FATAL 57P01 ("terminating connection due to administrator command") surfacing after returns zeros on Plaid API error (non-fatal) completed, in tests/integration/recurring-sync.test.ts. A pooled connection is still open when the testcontainer is torn down, so the reset lands as an unhandled error outside any test and Vitest exits non-zero.

Note this is not the hook-timeout problem — that one is fixed (hookTimeout: 30_000, PR #122) and no hook timed out in this run. Every assertion passed. Only the teardown ordering is wrong.

Likely fix: ensure the pool is drained before the container stops — await pool.end() in the afterEach/global teardown path rather than letting the container shutdown race an open client.

Why it is worth fixing rather than re-running

Both produce a red X that looks like a broken diff. Today that cost a full triage cycle on main and on two PRs, and the natural reaction — reverting the most recent change — is exactly wrong. See #103 for the related mutation-gate noise, and the pattern already recorded there of a revert being opened for a red the commit did not cause.

Activity

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions