Skip to content

test(profiles): oidc-account-linking "independent DuckDB handles" case fails on GitHub-hosted runners (2/2 runs) — blocks the hosted validation lane #2352

Description

@willgriffin

Context

On 2026-08-17 03:07Z the repo variable CI_HOSTED_FALLBACK_ENABLED was set to true (#2349, iac#1349 phase 0.5), moving test-suite.yml jobs to ubuntu-latest. The first hosted full-mode merge_group run (31990188105, PR #2344) passed, including @happyvertical/smrt-profiles#test. The next two hosted pull_request_target runs, on two unrelated PRs, both failed the same single test in packages/profiles/src/__tests__/oidc-account-linking.test.ts (~line 684):

OIDC Account Linking > createProfileFromOidc > serializes one issuer/subject with different emails across independent DuckDB handles

This test passes routinely on the metal fleet (arc-happyvertical-nodocker, 3 CPU pods) with the same shard shape: affected-package-tests, turbo run test --concurrency=2, VITEST_MAX_WORKERS=2, NODE_OPTIONS=--max-old-space-size=2048.

Evidence

  • Run 31990446549 (branch fix/issue-2341-template-rewrites, PR fix(core): emit portable paths in generated smrt-register manifests #2348 — diff touches only packages/core/src/vite-plugin/sveltekit-generator.*), job Validate Changes / affected-package-tests (1/3): Error: [vitest-pool]: Worker forks emitted error. Caused by: Error: Worker exited unexpectedly — silent fork death, no heap/signal line. Last test output before death was that test. 200/208 tests, 15/16 files, Failed: @happyvertical/smrt-profiles#test.
  • Run 31991297197 (branch perf/ui-client-bundle), job Validate Changes / affected-package-tests (2/3): the same test fails all 3 vitest retries with DatabaseError: Failed to execute raw queryoriginalError: 'Failed to execute prepared statement', sql: 'SELECT * FROM profiles WHERE id = $1'. Stack: @happyvertical/sql/src/duckdb.ts:1125 ← test identity-lookup Proxy (oidc-account-linking.test.ts:1338) ← ProfileCollection.queryRowsWithCache (core/src/collection.ts:1325) ← ProfileCollection.getrebindOidcProfileResult (src/auth/resolveIdentity.ts:519) ← withProvisioningLock (src/auth/oidcProvisioningCoordinator.ts:268). 1 failed / 207 passed.
  • This area was stabilised once already: Flaky DuckDB OIDC tests in smrt-profiles and smrt-users #2172 → PR test(oidc): stabilize DuckDB transaction fixtures #2191 (2026-08-02), "serialize row-count observations within caller-owned DuckDB transactions to avoid concurrent queries on one native connection".

Hypothesis

Not yet confirmed. The test wraps one getDatabase({ type: 'duckdb', url: ':memory:' }) handle in two proxies and runs two createProfileFromOidc calls concurrently. coordinateOidcProvisioning serialises the provisioning transaction and the post-commit rebind under an adapter-transaction lock, but new BackfillTracker({ db }).initialize() runs before the lock (comment: "Establish the shared backfill table outside the provisioning transaction"), so flow B's DDL/queries can interleave with flow A's transaction and rebind on the same native connection. DuckDB invalidates prepared statements on catalog changes and does not tolerate concurrent statements on one connection — consistent with both Failed to execute prepared statement and an outright native abort (the silent fork death). Faster 4-vCPU hosted runners plausibly shift the interleaving enough to hit it reliably where the slower fleet pods mostly don't. Also worth checking: getDatabase caches by URL, so vitest retries may reuse a poisoned connection — which would explain 3/3 retry failure.

Scope

  1. Reproduce, e.g. pnpm --filter @happyvertical/smrt-profiles exec vitest run src/__tests__/oidc-account-linking.test.ts -t "independent DuckDB handles" --repeat 20 on a 4+ core box.
  2. Then either move the pre-lock BackfillTracker.initialize() under the adapter lock for non-Postgres handles (or otherwise make the shared-connection path safe), or, if a shared connection across concurrent provisioning flows is not a supported production topology, fix the test to use genuinely serialised access. State which and why.
  3. Add a regression guard (repeat/shuffle probe as in test(oidc): stabilize DuckDB transaction fixtures #2191).

Not in scope: the hosted lane itself (#2349).

Acceptance

Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions