Skip to content

Fix two races breaking CI on main - #887

Merged
SawyerHood merged 1 commit into
mainfrom
bb/thr_fa39p7ygws
Jul 29, 2026
Merged

Fix two races breaking CI on main#887
SawyerHood merged 1 commit into
mainfrom
bb/thr_fa39p7ygws

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

CI on main was failing intermittently from two independent races. Both are ~2% flakes, so they broke main sporadically rather than outright — main run 30406401925 (#884) failed Tests (app), and run 30403074723 (#883) failed Tests (packages).

1. useResourcePagination dropped clicks

useResourcePagination mirrored the derived page back into state from a useEffect. That write-back captured the pre-interaction page, so when a viewport measurement changed pageSize, the queued effect flushed after a click and overwrote it:

  1. The measurement raises pageSize 10 → 15. That commit renders page=0 at pageSize=15 while the stored pageSize is still 10, and schedules the write-back with page=0 captured.
  2. Under load the effect hasn't flushed when the click lands, so setPage(1) writes {page: 1, pageSize: 15}.
  3. React then flushes the stale effect, which sees current.page (1) !== page (0) and resets state to {page: 0, pageSize: 15} — discarding the click.

Instrumenting setPage printed exactly this, and only in the failing runs:

[DIAG] setPage(1) closurePageSize=15 statePageSize=10 statePage=0 renderPageSize=15

This is a real product bug, not just a test flake: a user clicking Next in the instant after a list first measures its row height has the click silently ignored. It affects every paginated resource list — plugins, skills, browse, and automations.

State is now an anchor (the page plus the page size it was chosen under), written only by setPage or a new projection, with the rendered page derived from it. The projection reset moved from an effect to a render-time adjustment so it can't land after an interaction. A side benefit falls out: the anchor survives page-size changes, so remeasuring 15 → 10 rows returns you to the page you picked instead of stranding you on page 1.

2. Cross-package fixture race

plugins/workflows/src/cli.test.ts walks the entire checkout, and the packages CI shard runs every other package's tests concurrently in one turbo invocation. It enumerated packages/plugin-registry/.vendor-fixture-*/app.tsx and then read it after the plugin registry's afterAll deleted it → ENOENT.

The walk now skips dot-directories — covering VCS internals, tool caches, and the scratch trees siblings create in their own package roots (.vendor-fixture-*, and agent-runtime's .bb-codex-outside-*) — and tolerates a path deleted mid-walk. .git/.turbo dropped out of the explicit ignore set since the dot rule subsumes them.

Verification

  • The stress harness that reproduced the pagination flake at ~2/128 under 8× contention now runs 160/160 clean.
  • New apps/app/src/components/ui/resource-pagination.test.tsx pins the anchor semantics; its first test fails on the original hook (expected +0 to be 1) and passes on the fix. It lives in apps/app because shared-ui has no test runner, matching the existing apps/app/src/components/ui/theme.test.ts convention.
  • With a .vendor-fixture-PROOF/app.tsx containing the removed command in place, the workflows test passes (fixture ignored); with the same string in docs/cli-guide-and-skill.md it still fails as intended, so coverage is unchanged.
  • turbo run typecheck lint on app/shared-ui/workflows/automations: 0 errors.
  • turbo run test: app 292 files, workflows 13, automations 3, plugin-registry 1 — all pass.
  • Full CI packages shard: 47/48, with the two previously-racing packages passing concurrently.

Two failures left untouched, both pre-existing and local-only (they pass in CI): @bb/desktop#test needs an Electron binary that was never downloaded in my sandbox, and bb-plugin-tasks#test fails on window.localStorage being undefined — confirmed failing identically on a pristine HEAD.

🤖 Generated with Claude Code

Both were ~2% flakes, so they broke main intermittently rather than
outright: run 30406401925 (#884) failed Tests (app) and run 30403074723
(#883) failed Tests (packages).

useResourcePagination mirrored the derived page back into state from an
effect. That write-back captured the pre-interaction page, so when a
viewport measurement changed pageSize, the queued effect flushed after a
click and overwrote it. A user clicking Next in the instant after a list
first measures its row height had the click silently dropped, which is
what PluginsOverview's pagination test caught.

State is now an anchor -- the page plus the page size it was chosen
under -- written only by setPage or a new projection, with the rendered
page derived from it. The projection reset moved from an effect to a
render-time adjustment so it cannot land after an interaction. The anchor
also survives page-size changes, so remeasuring back returns the user to
the page they picked instead of stranding them on the first one.

The workflows CLI test walks the whole checkout while the packages CI
shard runs every other package concurrently, so it read
packages/plugin-registry/.vendor-fixture-*/app.tsx between the plugin
registry test creating and deleting it. The walk now skips
dot-directories -- covering VCS internals, tool caches, and the scratch
trees siblings create in their own package roots -- and tolerates a path
deleted mid-walk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit 0a84c37 into main Jul 29, 2026
10 checks passed
@SawyerHood
SawyerHood deleted the bb/thr_fa39p7ygws branch July 29, 2026 04:29
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.

1 participant