E2E: a connection per request, so a blocked test never reuses one wrangler dev closed
wrangler dev closes keep-alive connections idle for a few seconds, and e2e tests block the event loop for seconds at a time with execFileSync (`wrangler d1 execute`, git, tar). The close then sits unread, the next fetch reuses the dead connection and fails with "fetch failed" (UND_ERR_SOCKET): oauth.test.mjs on #261's first run, agent-setup.test.mjs on claude/agent-setup. test/e2e/fresh-connections.mjs sets undici's global dispatcher to one without keep-alive, imported first by every e2e file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M5tHXfgBQaLEPM9iUPrp8n
21 files changed+32−0
Files (21)
- apps/diffinery/package.json+1−0
- apps/diffinery/test/e2e/accounts.test.mjs+1−0
- apps/diffinery/test/e2e/agent-setup.test.mjs+1−0
- apps/diffinery/test/e2e/agentic.test.mjs+1−0
- apps/diffinery/test/e2e/e2e.test.mjs+1−0
- apps/diffinery/test/e2e/early-access.test.mjs+1−0
- apps/diffinery/test/e2e/email.test.mjs+1−0
- apps/diffinery/test/e2e/fresh-connections.mjs+10−0
- apps/diffinery/test/e2e/graphql.test.mjs+1−0
- apps/diffinery/test/e2e/moderation.test.mjs+1−0
- apps/diffinery/test/e2e/oauth.test.mjs+1−0
- apps/diffinery/test/e2e/passkeys.test.mjs+1−0
- apps/diffinery/test/e2e/registry.test.mjs+1−0
- apps/diffinery/test/e2e/review.test.mjs+1−0
- apps/diffinery/test/e2e/security-limits.test.mjs+1−0
- apps/diffinery/test/e2e/security-txt.test.mjs+1−0
- apps/diffinery/test/e2e/security.test.mjs+1−0
- apps/diffinery/test/e2e/status.test.mjs+1−0
- apps/diffinery/test/e2e/tasks.test.mjs+1−0
- apps/diffinery/test/e2e/terminals.test.mjs+1−0
- pnpm-lock.yaml+3−0
modifiedapps/diffinery/package.json+1−0View file
| @@ -46,6 +46,7 @@ | ||
| 46 | 46 | "react": "19.2.0", |
| 47 | 47 | "react-dom": "19.2.0", |
| 48 | 48 | "typescript": "^5.9.3", |
| 49 | + "undici": "7.29.1", | |
| 49 | 50 | "vitest": "^4.1.0", |
| 50 | 51 | "wrangler": "^4.149.0" |
| 51 | 52 | } |
modifiedapps/diffinery/test/e2e/accounts.test.mjs+1−0View file
| @@ -7,6 +7,7 @@ | ||
| 7 | 7 | // username waits 90 days), a site administrator deleting someone's account, exporting an account (a tar |
| 8 | 8 | // archive with a clonable Git bundle and the issues you wrote), and granting and revoking site |
| 9 | 9 | // administration (signed in recently, audited, never the last site administrator). |
| 10 | +import "./fresh-connections.mjs"; | |
| 10 | 11 | import { spawn, execFileSync } from "node:child_process"; |
| 11 | 12 | import { createHash, randomBytes } from "node:crypto"; |
| 12 | 13 | import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/agent-setup.test.mjs+1−0View file
| @@ -12,6 +12,7 @@ | ||
| 12 | 12 | // only what a person approved and never a workflow change, turning merging off, and revoke, regenerate |
| 13 | 13 | // and renew. gh only talks HTTPS, so a stand-in gh (below) does what gh does with GH_ENTERPRISE_TOKEN and |
| 14 | 14 | // GH_HOST; the real gh runs against diffinery.com in the live check. |
| 15 | +import "./fresh-connections.mjs"; | |
| 15 | 16 | import { spawn, execFileSync } from "node:child_process"; |
| 16 | 17 | import { createHash, randomBytes } from "node:crypto"; |
| 17 | 18 | import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/agentic.test.mjs+1−0View file
| @@ -6,6 +6,7 @@ | ||
| 6 | 6 | // request waiting for a person, an agent's failing pull request, approvals and |
| 7 | 7 | // merges moving items between sections, the REST API, the MCP tool, an agent's |
| 8 | 8 | // own view and the dashboard page. |
| 9 | +import "./fresh-connections.mjs"; | |
| 9 | 10 | import { spawn, execFileSync } from "node:child_process"; |
| 10 | 11 | import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; |
| 11 | 12 | import { tmpdir } from "node:os"; |
modifiedapps/diffinery/test/e2e/e2e.test.mjs+1−0View file
| @@ -8,6 +8,7 @@ | ||
| 8 | 8 | // check runs, branch protection with required checks and human review for |
| 9 | 9 | // agent-authored PRs, merging, the REST contents API, the MCP server and |
| 10 | 10 | // profile pictures (WASM image processing in the Worker). |
| 11 | +import "./fresh-connections.mjs"; | |
| 11 | 12 | import { spawn, spawnSync, execFileSync } from "node:child_process"; |
| 12 | 13 | import { mkdtempSync, writeFileSync, mkdirSync, readFileSync, existsSync, rmSync, openSync } from "node:fs"; |
| 13 | 14 | import { tmpdir } from "node:os"; |
modifiedapps/diffinery/test/e2e/early-access.test.mjs+1−0View file
| @@ -10,6 +10,7 @@ | ||
| 10 | 10 | // identity only applies to requests addressed to this machine. |
| 11 | 11 | // |
| 12 | 12 | // pnpm --filter @diffinery/app exec node --test test/e2e/early-access.test.mjs |
| 13 | +import "./fresh-connections.mjs"; | |
| 13 | 14 | import { spawn, execFileSync } from "node:child_process"; |
| 14 | 15 | import { createHash, randomBytes } from "node:crypto"; |
| 15 | 16 | import { mkdtempSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/email.test.mjs+1−0View file
| @@ -11,6 +11,7 @@ | ||
| 11 | 11 | // through, failed workflow run emails, and the suppression list: signed bounce |
| 12 | 12 | // and complaint intake, no email to a suppressed address, lifting it again; the |
| 13 | 13 | // morning digest on its schedule (phase 5c) and Today at phone width. |
| 14 | +import "./fresh-connections.mjs"; | |
| 14 | 15 | import { spawn, execFileSync } from "node:child_process"; |
| 15 | 16 | import { mkdtempSync, writeFileSync, mkdirSync, existsSync, rmSync } from "node:fs"; |
| 16 | 17 | import { tmpdir } from "node:os"; |
addedapps/diffinery/test/e2e/fresh-connections.mjs+10−0View file
| @@ -1,0 +1,10 @@ | ||
| 1 | +// Every fetch in an end-to-end test process gets a connection of its own (imported for that alone). | |
| 2 | +// | |
| 3 | +// wrangler dev closes a keep-alive connection that sits idle for a few seconds, and tests block the | |
| 4 | +// event loop for seconds at a time (execFileSync: `wrangler d1 execute`, git, tar). The close then | |
| 5 | +// waits unread, the next fetch reuses the dead connection and fails with "fetch failed" (other side | |
| 6 | +// closed, UND_ERR_SOCKET), and so does the test. A new localhost connection per request costs well under | |
| 7 | +// a millisecond; Node's fetch and WebSocket use this dispatcher. | |
| 8 | +import { Agent, setGlobalDispatcher } from "undici"; | |
| 9 | + | |
| 10 | +setGlobalDispatcher(new Agent({ pipelining: 0 })); |
modifiedapps/diffinery/test/e2e/graphql.test.mjs+1−0View file
| @@ -6,6 +6,7 @@ | ||
| 6 | 6 | // GitHub's error shape. |
| 7 | 7 | // |
| 8 | 8 | // pnpm --filter @diffinery/app exec node --test test/e2e/graphql.test.mjs |
| 9 | +import "./fresh-connections.mjs"; | |
| 9 | 10 | import { spawn, execFileSync } from "node:child_process"; |
| 10 | 11 | import { createHash, randomBytes } from "node:crypto"; |
| 11 | 12 | import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/moderation.test.mjs+1−0View file
| @@ -8,6 +8,7 @@ | ||
| 8 | 8 | // (or the blocking organization's) repositories but still can everywhere else; a suspended person's |
| 9 | 9 | // token and session stop at once, their repositories, profile and comments disappear for everyone |
| 10 | 10 | // but site administrators, and unsuspending restores it all; a minimized comment shows collapsed. |
| 11 | +import "./fresh-connections.mjs"; | |
| 11 | 12 | import { spawn, execFileSync } from "node:child_process"; |
| 12 | 13 | import { createHash, generateKeyPairSync, randomBytes } from "node:crypto"; |
| 13 | 14 | import { mkdtempSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/oauth.test.mjs+1−0View file
| @@ -7,6 +7,7 @@ | ||
| 7 | 7 | // client metadata document as the client_id, the consent page (signed out, unknown app, wrong redirect, |
| 8 | 8 | // cancel, acting as yourself or an agent, admin never preselected), PKCE, codes working once, MCP calls |
| 9 | 9 | // with the token, refresh rotation and reuse, confidential clients, revocation, and Connected apps. |
| 10 | +import "./fresh-connections.mjs"; | |
| 10 | 11 | import { spawn, execFileSync } from "node:child_process"; |
| 11 | 12 | import { createHash, randomBytes } from "node:crypto"; |
| 12 | 13 | import { mkdtempSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/passkeys.test.mjs+1−0View file
| @@ -11,6 +11,7 @@ | ||
| 11 | 11 | // single-use invites and links, and refusing cross-site and token-driven calls. |
| 12 | 12 | // Open sign-up (SIGNUP=open) runs on a second Worker: an emailed code, then a |
| 13 | 13 | // passkey, and the sign-up pages answering 404 while it is off. |
| 14 | +import "./fresh-connections.mjs"; | |
| 14 | 15 | import { spawn, execFileSync } from "node:child_process"; |
| 15 | 16 | import { createHash, createHmac, randomBytes } from "node:crypto"; |
| 16 | 17 | import { mkdtempSync, readFileSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/registry.test.mjs+1−0View file
| @@ -8,6 +8,7 @@ | ||
| 8 | 8 | // manifests by tag and digest, indexes, referrers, tag listing, cross-repository mounts, |
| 9 | 9 | // token scopes, the packages REST API, the job token's `packages` permission, linking |
| 10 | 10 | // packages to repositories, and docker login/push/pull. |
| 11 | +import "./fresh-connections.mjs"; | |
| 11 | 12 | import { spawn, spawnSync, execFileSync } from "node:child_process"; |
| 12 | 13 | import { createHash, randomBytes } from "node:crypto"; |
| 13 | 14 | import { mkdtempSync, writeFileSync, mkdirSync, rmSync, existsSync, readFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/review.test.mjs+1−0View file
| @@ -9,6 +9,7 @@ | ||
| 9 | 9 | // read-only sandbox, autofix pushing to the pull request's branch, the |
| 10 | 10 | // re-review marking the finding fixed, Fix it, asking for a review, and the |
| 11 | 11 | // spend limits (model requests per review, the repository's daily limit). |
| 12 | +import "./fresh-connections.mjs"; | |
| 12 | 13 | import { spawn, execFileSync } from "node:child_process"; |
| 13 | 14 | import { mkdtempSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, readFileSync, existsSync } from "node:fs"; |
| 14 | 15 | import { tmpdir } from "node:os"; |
modifiedapps/diffinery/test/e2e/security-limits.test.mjs+1−0View file
| @@ -3,6 +3,7 @@ | ||
| 3 | 3 | // invitations by username, per-account quotas, kill switches and the Markdown image proxy. |
| 4 | 4 | // |
| 5 | 5 | // pnpm --filter @diffinery/app e2e |
| 6 | +import "./fresh-connections.mjs"; | |
| 6 | 7 | import { spawnSync, execFileSync } from "node:child_process"; |
| 7 | 8 | import { createHash, randomBytes } from "node:crypto"; |
| 8 | 9 | import { writeFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/security-txt.test.mjs+1−0View file
| @@ -5,6 +5,7 @@ | ||
| 5 | 5 | // audited triage; the other has SECURITY_CONTACT set (every field, the form first). |
| 6 | 6 | // |
| 7 | 7 | // pnpm --filter @diffinery/app exec node --test test/e2e/security-txt.test.mjs |
| 8 | +import "./fresh-connections.mjs"; | |
| 8 | 9 | import { spawn, execFileSync } from "node:child_process"; |
| 9 | 10 | import { createHash, randomBytes } from "node:crypto"; |
| 10 | 11 | import { mkdtempSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/security.test.mjs+1−0View file
| @@ -22,6 +22,7 @@ | ||
| 22 | 22 | // merge queue) where a person allowed it and a required check gates it, never by bypass. |
| 23 | 23 | // Continued in security-limits.test.mjs (environment reviewers, quotas, kill switches, the image |
| 24 | 24 | // proxy), which runs beside this file on a server of its own; the setup is in security-harness.mjs. |
| 25 | +import "./fresh-connections.mjs"; | |
| 25 | 26 | import { spawnSync, execFileSync } from "node:child_process"; |
| 26 | 27 | import { createHash, randomBytes } from "node:crypto"; |
| 27 | 28 | import { mkdtempSync, writeFileSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/status.test.mjs+1−0View file
| @@ -4,6 +4,7 @@ | ||
| 4 | 4 | // 404 for everyone else, the audit log, and the hosted compute pause showing as maintenance. |
| 5 | 5 | // |
| 6 | 6 | // pnpm --filter @diffinery/app exec node --test test/e2e/status.test.mjs |
| 7 | +import "./fresh-connections.mjs"; | |
| 7 | 8 | import { spawn, execFileSync } from "node:child_process"; |
| 8 | 9 | import { createHash, randomBytes } from "node:crypto"; |
| 9 | 10 | import { mkdtempSync, rmSync } from "node:fs"; |
modifiedapps/diffinery/test/e2e/tasks.test.mjs+1−0View file
| @@ -10,6 +10,7 @@ | ||
| 10 | 10 | // security rules: no token, agent or sandbox can start or steer a task, task |
| 11 | 11 | // sandboxes are closed to the sandbox API, and a run's model token stops |
| 12 | 12 | // working when the run ends. |
| 13 | +import "./fresh-connections.mjs"; | |
| 13 | 14 | import { spawn, execFileSync } from "node:child_process"; |
| 14 | 15 | import { mkdtempSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, readFileSync, existsSync } from "node:fs"; |
| 15 | 16 | import { tmpdir } from "node:os"; |
modifiedapps/diffinery/test/e2e/terminals.test.mjs+1−0View file
| @@ -2,6 +2,7 @@ | ||
| 2 | 2 | // (agents-harness.mjs). In a file of their own so each file stays well under the test runner's per-file limit. |
| 3 | 3 | // |
| 4 | 4 | // pnpm --filter @diffinery/app e2e |
| 5 | +import "./fresh-connections.mjs"; | |
| 5 | 6 | import { spawn, execFileSync } from "node:child_process"; |
| 6 | 7 | import { mkdtempSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, readFileSync, existsSync } from "node:fs"; |
| 7 | 8 | import { tmpdir } from "node:os"; |
modifiedpnpm-lock.yaml+3−0View file
| @@ -121,6 +121,9 @@ | ||
| 121 | 121 | typescript: |
| 122 | 122 | specifier: ^5.9.3 |
| 123 | 123 | version: 5.9.3 |
| 124 | + undici: | |
| 125 | + specifier: 7.29.1 | |
| 126 | + version: 7.29.1 | |
| 124 | 127 | vitest: |
| 125 | 128 | specifier: ^4.1.0 |
| 126 | 129 | version: 4.1.11(@types/node@22.20.5)(vite@8.3.4(@types/node@22.20.5)(esbuild@0.25.11)(yaml@2.9.1)) |