fix(database): deterministic db init - bounded retry on transient Prisma schema-engine crash#98
Conversation
…gine crash `db init`/`db migrate` intermittently failed in the CLI scaffold e2e with: Error: Schema engine exited. Error [ERR_STREAM_PREMATURE_CLOSE]: schema-engine-windows.exe ... cli can-connect-to-database Premature close Root cause is NOT a Postgres-readiness race (Aspire `.waitFor(postgres)` already gates the migrate executable on a healthy DB, confirmed in the captured logs). It is the Prisma 7.x schema engine — a separate subprocess Prisma spawns — dying with a premature stream close on Windows during its `can-connect-to-database` preflight (engine-binary first-touch / AV scan / cold cache). It surfaced after the Aspire 13.4 TS AppHost migration (677d540) bumped Prisma engines to 7.8.0. The crash happens before any migration SQL is written, so re-running is idempotent. Fix: bound a signature-scoped retry around the Prisma invocation in the migrate runner. Only the transient premature-close / "Schema engine exited" signature is retried (up to 4 attempts, linear backoff); every other failure returns immediately so real schema/SQL errors are never masked. stderr is captured for classification and mirrored back so logs are never swallowed; truly interactive runs keep inherited stderr and are never retried. - packages/database/scripts/migrate.ts: extract injectable spawn + bounded, classified retry (runPrismaWithRetry, isRetriableMigrationFailure). - packages/database/scripts/mod.ts: export the new testable surface. - packages/database/tests/migrate-retry_test.ts: cover signature matching, retry-then-succeed, bounded stop, no-mask of real errors, interactive single-shot, first-attempt success. Evidence: 5 unit tests green; 18/18 consecutive passes driving the real patched runMigration against a fresh Postgres with full state reset per iteration; scoped check/lint/fmt clean; deno.lock untouched (no new deps). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fq9V7ujx7e1rWXi57qkPG
|
@openhands-agent model=openrouter/qwen/qwen3.7-max provider=openrouter output=pr-comment iterations=800 use harness IMPL-EVAL — PR #98
|
OpenHands Agent — CompletedModel: openrouter/qwen/qwen3.7-max IMPL-EVAL Summary — PR #98Verdict: PASSAll evaluation criteria satisfied. The bounded retry on transient Prisma schema-engine crash is correctly implemented, narrowly scoped, and does not mask real errors. Evaluation Criteria1. Root-Cause Soundness ✅Verified
Test 2. Idempotency Claim ✅The retry wraps 3. No Masking / Honest Logs ✅
4. Type Soundness ✅Grep scan for TypeScript suppression patterns ( Only match was in a comment ("spawns as a child process"), not a cast. 5. Lock Hygiene ✅
Files changed:
6. Tests ✅Ran
Scoped validation wrappers:
7. Scope ✅Changes confined to:
No out-of-scope edits. Package boundaries respected. ChangesCore Implementation (
|
…hurn + trace The IMPL-EVAL (PASS, qwen3.7-max) pushed two artifact commits onto this branch: a +193/-88 deno.lock re-resolution and a run-trace dir. The fix commit 4335a93 added no imports, so the lock should not change. This forward commit reverts the lock re-resolution and removes the trace so the squash-merge into feat/framework-prime-time carries only the fix (and resolves the lock conflict). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fq9V7ujx7e1rWXi57qkPG
What
Make
db init(Prismamigrate dev) deterministic against an intermittent Windows schema-engine crash that has been failing thescaffold.runtimee2e gate (and OpenHands eval runs) at random since ~Jun 16.Root cause (not a readiness race)
The failure is not the Postgres-provisioning race we suspected — Aspire's
.waitFor(postgres)already gates the migrate executable on a healthy DB (the logs showWaiting for resource 'postgres' to become healthy→Finished waitingbefore the engine runs).The real failure is the Prisma 7.x schema-engine child process (
schema-engine-windows.exe) dying withERR_STREAM_PREMATURE_CLOSE/Premature closeduring itscan-connect-to-databasepreflight on Windows (engine-binary first-touch / AV scan / coldnode_modules/.denocache). The crash happens before any migration SQL is written, so a retry is idempotent.Why it started ~3 days ago:
677d5405(Aspire 13.4 AppHost migration, Jun 16) bumped@prisma/enginesto7.8.0, surfacing pre-existing Windows schema-engine flakiness as a frequent blocker.Fix
runPrismaWithRetryinpackages/database/scripts/migrate.ts— bounded retry (default 4 attempts, linear backoff) that fires only on the transient signature (isRetriableMigrationFailure:ERR_STREAM_PREMATURE_CLOSE/Premature close/Schema engine exited). Every non-transient failure returns immediately, so real schema/SQL errors are never masked. Spawn + sleep are injected for testability; interactive runs keep inherited stderr and are never retried.packages/database/scripts/migrate.ts— retry wrapper around the threemigratespawnspackages/database/scripts/mod.ts— export the testable surfacepackages/database/tests/migrate-retry_test.ts— 5 new testsZero casts. No new imports → no dependency-graph change.
deno.lockuntouched.Evidence
runMigration(the exact non-interactive e2e path) against a fresh Postgres 17 container with full DB drop/recreate + migration wipe per iteration.packages/database.Verification note
The full
scaffold.runtimesuite (~3–5 min, 47 steps) was deliberately not used as the determinism proof: the flake is low-rate per run, so a single green run proves nothing and looping it enough to be statistically meaningful is the expensive path. The unit tests against the verbatim error + the 18/18 isolated loop are the determinism proof. IMPL-EVAL / a final headlinescaffold.runtimerun is the appropriate confidence check.🤖 Generated with Claude Code