Skip to content

ci: read each shard's docker services from the test sources - #44993

Open
robobun wants to merge 1 commit into
mainfrom
robobun/e514b6e9/derive-docker-prestart-from-sources
Open

robobun wants to merge 1 commit into
mainfrom
robobun/e514b6e9/derive-docker-prestart-from-sources

Conversation

@robobun

@robobun robobun commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/docker/prestart-map.mjs is a hand-written prefix list. It decides which containers a shard starts at launch, which files run last and which stay out of the bun test --parallel batch. It is wrong for 11 of the 14 docker-backed files added since it landed.
  • Main build 124219: 56 containers start inside a test file (coordinator: ensuring postgres_plain mid-run, 12.9 to 30 s), 29 inside the batch. Only Linux agent time pays: 3.2%.

Fix

  • scripts/docker-services.ts reads what a test file asks for from its source. The runner uses that for all three decisions. The map is deleted.
  • A request the rule cannot name still starts on first request, and a launch failure reaches the test that asks.
  • Verified: test/internal/docker-services.test.ts (4 of 15 fail on main's wiring). CI of this PR (build 124593): no container starts inside a test file, launch starts 444 to 309.
  • Self-reviewed: 15 concerns raised, 15 addressed.

Background

  • The coordinator is one process per shard that runs every docker compose command. Tests reach it over a unix socket.
  • Considered 11 more map rows: the next new file misses again, and a miss is no longer "correct, just slower" (the map's header) because the map now gates the batch.

Downsides

  • Each runner process reads its shard's bun test files once: median 108 reads, about 41 ms per Linux shard.
  • 14 shards start more at launch. For 4 it is the first launch: about 6 s slower setup.
  • 14 mock-server files can enter the batch at the next allowlist regeneration.
Notes

Measured in the CI of this PR

The numbers come from the 140 Linux test job logs of two builds: main build 124219 and build 124593 of this PR. A launch start is a coordinator: ensuring <service> line in the first 2 s after COORDINATOR_READY. A start on request is the same line later in the log.

main 124219 this PR 124593
containers that start inside a test file 56 (973.8 s of waits, on 55 shards) 0
launch starts 444 309
launch starts that no test asks for 231 43
the same on the four x64 lanes 113 0
most services on one shard 6 5
launch failures (autobahn on aarch64) 3 3
wall time of the 11 files, 7 lanes, summed 973 s 42 s
Linux test agent time 30,242 s 29,300 s
  • coordinator: ensuring mariadb_plain is at launch on each of the 7 shards that run sql-mysql-row-decode-error.test.ts. coordinator: ensuring postgres_plain is at launch on each of the 7 shards that run postgres-bind-encode-throw.test.ts and on each of the 7 that run sql-begin-commit-rolled-back.test.ts. For the 11 files, 77 of 77 runs have every service they ask for started at launch.
  • The two builds do not place files on the same shards: the test list differs by 4 files, and about a quarter of the serial and batch files are on another shard. So the table compares whole builds. On the shards of build 124593, main's map gives 474 launch starts, misses 51 requested services and starts 259 that no test asks for.
  • The difference in Linux agent time is near the 974 s of waits, but Windows agent time also differs by 2% between the two builds with no docker, so read it as an indication only.
  • The build does not end earlier. In both builds a darwin job is the last test job, 16 to 20 minutes after the last Linux one.
  • No person reported this. The observations come from CI logs.
  • No docker daemon runs in the development container (docker info fails there), so docker compose up did not run there. Before the CI run, a static replay of the logs of build 124219 gave launch starts / first start on request / never requested of 444 / 56 / 231 for main, 503 / 0 / 234 for main with 11 more map rows, and 308 / 0 / 39 for this PR.

Per-file wall time, before (build 124219) and after (build 124593)

file build debian x64 debian x64-asan debian aarch64 ubuntu x64 ubuntu aarch64 alpine x64 alpine aarch64
sql-mysql-row-decode-error.test.ts before 13.59 s 16.16 s 19.22 s 14.22 s 17.34 s 17.28 s 18.18 s
after 0.07 s 0.37 s 0.07 s 0.06 s 0.07 s 0.08 s 0.07 s
postgres-bind-encode-throw.test.ts before 14.94 s 20.24 s 12.91 s 14.67 s 14.46 s 0.14 s 0.19 s
after 0.18 s 0.64 s 0.15 s 0.17 s 0.16 s 0.15 s 0.16 s
sql-begin-commit-rolled-back.test.ts before 0.05 s 0.41 s 0.04 s 0.05 s 0.04 s 23.76 s 21.88 s
after 0.23 s 0.45 s 0.16 s 0.24 s 0.14 s 0.14 s 0.20 s

The other 8 of the 11 files, slowest lane before and after: postgres-bytea-bind 18.38 s and 0.48 s, postgres-datestyle 20.41 s and 0.31 s, postgres-listen-notify 19.06 s and 5.36 s (56 tests of its own work), postgres-prepared-pipeline-reorder 14.91 s and 0.72 s, postgres-row-decode-error 18.03 s and 0.43 s, sql-empty-column-name 21.79 s and 0.79 s, sql-reserve-abort 17.44 s and 0.30 s, sql-statement-cache-hash-collision 31.14 s and 1.16 s.

In build 124219 on debian x64 shard 7, the log has coordinator: ensuring mariadb_plain inside the first file and coordinator: mariadb_plain ready 13.5 s later. The map starts three mysql services for that file and it asks for mariadb_plain. The other two files match no row, so they run inside the bun test --parallel batch. On debian x64 shard 1 the batch of 71 files waits about 15 s for one postgres_plain container. In build 124593 the six files that were in the batch run after the serial files.

What the rule reads

  • A file is docker-backed when it calls describeWithContainer(), imports test/docker/index.ts, or imports a helper module that helperServices lists. Today that list has one entry, test/js/valkey/test-utils.ts, which 11 valkey test files import.
  • The rule names a service when the file holds it as a string literal: image: "<service>" in a describeWithContainer() file, or the first argument of a call in a file that imports test/docker/index.ts. mysql:8 and mysql:9 count as mysql_plain and mysql_native_password, because describeWithContainer() chooses between them at run time.
  • The service names are in the new test/docker/service-names.mts. ServiceName in test/docker/index.ts is now the type of that list. A test asserts that the list equals the services: keys of docker-compose.yml.
  • On today's tree the rule finds 55 docker-backed files among 2,229 bun test files, and it names at least one service for each of them.
  • A computed service name is not read. The file is still docker-backed, and the coordinator starts the service on first request. Any file that calls describeWithContainer() stays out of the batch, as in main's allowlist generator (ci: re-land the per-file parallel allowlist with a denylist of every file that has failed in the batch #39373).
  • A request through a helper module that is not listed is not seen at all. That file is not docker-backed for the runner, so it can run inside the batch and wait for its container there.

What changes for files that did not have the problem

  • Docker-backed files: 59 in the runner and 69 in the allowlist generator on main, 55 and 55 here.
  • 10 files gain the flag: postgres-bind-encode-throw, postgres-bytea-bind, postgres-datestyle, postgres-listen-notify, postgres-prepared-pipeline-reorder, postgres-row-decode-error, sql-begin-commit-rolled-back, sql-empty-column-name, sql-reserve-abort, sql-statement-cache-hash-collision (all in test/js/sql).
  • 15 files keep the flag with a corrected list. 14 sql-mysql* files get the one service they ask for and not three. sql-mysql-row-decode-error gets mariadb_plain.
  • 6 files leave the batch: postgres-bind-encode-throw, postgres-bytea-bind, postgres-row-decode-error, sql-begin-commit-rolled-back, sql-empty-column-name, sql-reserve-abort. No file enters it today.
  • 14 mock-server files lose the flag: js/sql/postgres-binary-numeric-digit-range, js/sql/sql-mysql-{auth-short-nonce, clean-reentry, columns-realloc-oom, duplicate-auth-switch, prepare-ok-zero-statement-id, tls-plaintext-injection}, js/valkey/{reliability/resp-nesting-depth, valkey-gc, valkey-incremental-scan, valkey-tls-verify}, js/web/websocket/websocket-proxy-{close-reentrancy, tunnel-client-leak, tunnel-upgrade-leak}.
  • Those 14 stay out of the batch today, because the checked-in test/parallel-allowlist.json (generated 2026-08-17) excludes 10 of them and does not list js/valkey or js/valkey/reliability. scripts/update-parallel-allowlist.mjs now uses the same rule. At the next regeneration the 14 qualify by the normal rules (fast on every lane, no flaky annotation, not in the denylist). None of them has run in the batch, and the CI of this PR does not show that change. This PR does not regenerate the snapshot. It changes one phrase of its _meta.rule.

Cost

  • Runner: one read for each bun test file of the shard, memoised. Over the 140 shard lists of build 124219: median 108 reads (96 to 122), median 1.73 MB, 0 failed opens, 15,316 reads per build. Main: 0 reads.
  • File syscalls on test sources for debian x64 shard 7: 576 (108 openat, 355 read, 108 close, 5 statx). Main: 0. strace is not in the container, so a small ptrace counter made this count.
  • Time of the rule before the coordinator spawn, 20 interleaved fresh Node processes, same shard: main 3.7 ms median (2.4 to 4.7), this PR 45.1 ms median (27.1 to 63.0).
  • A local run with no shards reads 2,188 files (34.8 MB) in 230 to 260 ms. Main: 25 ms.
  • Coordinator: COORDINATOR_READY comes 50 ms after spawn on main and here (release build). Its argv for that shard is 1 service name (14 bytes). It was 301 test paths (15,259 bytes).
  • One helper entry in place of an import follower: a one-hop follower gives the same 308 launch starts with 2,199 more reads and 20,593 failed opens per build. Limited to static imports it is 2,527 reads and 4,252 failed opens.
  • Launch side: a launch slows the shard setup (coordinator ready to the first test file). Build 124219: median 15.2 s on the 13 shards that launch nothing, 21.5 s on the 127 that launch. Build 124593: 12.1 s on the 5 shards that launch nothing, 17.8 s on the 35 that launch one service, 22.6 s over all 135 that launch. On 14 shards of build 124593 the launch list is longer than the map gives for the same files. 4 of those (alpine) launch nothing with the map and postgres_plain here.

Tests

  • test/internal/docker-services.test.ts has 15 tests. It takes about 1 s on a release build and 9 to 15 s on a debug build, where three coordinators start at the same time (4.5 to 6.6 s each). Those three tests take a 30 s timeout on a debug build only. CI passes --timeout=90000.
  • With main's runner, coordinator and map, and the new modules in place, 4 tests fail: the coordinator launch test, the launch failure test and both runner tests. With main alone the file does not load.
  • Five tests start a process. Three start test/docker/coordinator.ts. Two run scripts/runner.node.ts with stand-ins for bun, docker and who on PATH. ci: serialize docker service startup through a per-shard coordinator #32033 removed a coordinator test that used a docker shim. These five are kept because they need no docker, and they are the only tests that reach the spawn line of the runner and the launch loop of the coordinator. If a maintainer prefers the ci: serialize docker service startup through a per-shard coordinator #32033 decision, remove all five together.
  • Also run: test/internal/source-lints/, test/internal/parallel-allowlist.test.ts, test/internal/runner-junit.test.ts, test/internal/ci-slowest-tests.test.ts, tsc -p scripts/tsconfig.json, and the new file through scripts/runner.node.ts with exception-check and leak validation on.

Open PRs that edit or import the deleted map

Not in this PR

  • 43 launch starts in build 124593 have no request, all on the three aarch64 lanes. Most of them come from files that test isDockerEnabled(), which returns false on Linux arm64 (test/harness.ts). test: resolve this harness TODO #25212 removes that return. A rule that skips those files misses 11 requests in the replay, so the rule does not look at that guard.
  • The docker-backed files are still spread over most shards (135 of 140 in build 124593). To place them on fewer shards is a possible next step, and it needs this rule as its input.
  • test/expected-durations.json still counts the container waits in the time of these files until its next refresh.
  • The container that starts at launch and that no test asks for is still never stopped.

Scope

scripts/update-parallel-allowlist.mjs also reads the deleted map, so it moves to the rule. The rule is in scripts/ because the runner is the one that evaluates it. Two tsconfig entries for the deleted file are removed. No file under src/ and no sql test file changes.


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/internal/docker-services.test.ts

The runner now reads which docker-compose services a test file asks for
from the source of that file (scripts/docker-services.ts). It uses the
answer for the docker-last sort and the parallel bucket gate, and it
passes the services of the shard's files to the coordinator, which
starts them at launch.

The hand-maintained prefix map (test/docker/prestart-map.mjs) is
removed. scripts/update-parallel-allowlist.mjs uses the same rule.
test/docker/service-names.mts holds the service names, and ServiceName
is the type of that list.
@robobun

robobun commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 10:54 AM PT - Oct 11th, 2026

✅ @robobun, your commit 052411ecdcc004fd2de07336341c6bdf0e3617d2 passed in Build #124593! 🎉


🧪   To try this PR locally:

bunx bun-pr 44993

That installs a local version of the PR into your bun-44993 executable, so you can run:

bun-44993 --bun

@robobun

robobun commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. CI is green on 052411e (build 124593).

  • Reproduced from the Linux job logs of main build 124219: 56 containers start inside a test file, on 55 of 140 shards.
  • In the CI of this PR the count is 0. coordinator: ensuring postgres_plain and coordinator: ensuring mariadb_plain are at launch on each shard that runs the reported files. The 11 files take 42 s in total over the 7 Linux lanes. Before, they took 973 s.
  • bun bd test test/internal/docker-services.test.ts passes (15 tests). With the runner and the coordinator of main, 4 of the 15 fail.

@robobun
robobun marked this pull request as ready for review October 15, 2026 13:09
@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 19 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4c87cb77-0388-4088-92af-739ef9fd33ff
📥 Commits

Reviewing files that changed from the base of the PR and between 6c1eb06 and 052411e.

📒 Files selected for processing: 12
  • scripts/docker-services.ts
  • scripts/runner.node.ts
  • scripts/tsconfig.json
  • scripts/update-parallel-allowlist.mjs
  • test/docker/README.md
  • test/docker/coordinator.ts
  • test/docker/index.ts
  • test/docker/prestart-map.mjs
  • test/docker/service-names.mts
  • test/internal/docker-services.test.ts
  • test/parallel-allowlist.json
  • test/tsconfig.json
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment on lines +125 to +127
try {
source = readFileSync(`${testsDir}/${path}`, "utf8");
} catch {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) A docker-backed test file that cannot be read for any reason other than not existing is silently treated as non-docker, so the generator can allowlist it and the runner puts it in the parallel batch. The empty catch {} at scripts/docker-services.ts:127 maps every readFileSync error (EACCES, EISDIR, EMFILE, EIO) to "asks for nothing". Fix: treat only ENOENT as the benign no-request case and rethrow (or at least log) every other error, for both callers (runner.node.ts needsDockerService and update-parallel-allowlist.mjs needsDockerService). On the base branch the allowlist generator read the file without a try and failed loudly on any unreadable file.

Why this was flagged

A test file listed by getTests (scripts/runner.node.ts:2501) or listBunTestFiles (scripts/update-parallel-allowlist.mjs:53) exists but readFileSync fails with an errno other than ENOENT, for example a permission problem in the checkout or an EMFILE/EIO on the agent. requestsOf at scripts/docker-services.ts:125-128 catches every error, returns none (backed: false) and caches that answer for the process. For a file that really calls describeWithContainer this makes needsDockerService false: scripts/runner.node.ts:1326 lets it into the bun test --parallel bucket and scripts/runner.node.ts:744 never passes its service to the coordinator, so the container starts inside the batch, the exact cost the PR removes. In scripts/update-parallel-allowlist.mjs:159 the same answer writes the file into test/parallel-allowlist.json as eligible, a persistent artifact. On the base branch the generator read the file with a bare readFileSync (base scripts/update-parallel-allowlist.mjs:162) and crashed on any read error, and the runner used a path prefix map that needed no read.

Verification: Triggering condition: readFileSync on a listed test file fails with an errno other than ENOENT in the runner or allowlist-generator process. At scripts/docker-services.ts:125-129 every error becomes none (backed: false, line 115) and is cached for the process. Regression vs base: the base generator's usesContainer had no catch, so a read error aborted the script loudly.

Comment on lines +322 to +349
describe.skipIf(!isLinux || !nodeExe())("scripts/runner.node.ts", () => {
/** Runs the runner over `testPaths` with stand-ins for bun, docker and who, and reports what it started. */
async function runRunner(testPaths: string[]) {
using dir = tempDir("docker-services-runner", {
"docker": `#!/bin/sh\n[ "$*" = "compose version" ]\n`,
// A CI runner stays alive while somebody is logged in.
"who": `#!/bin/sh\n`,
// Stands in for the bun under test. It answers the runner's probe, records how the runner starts
// the coordinator and which test files it runs, and fails the ci-remap-server install so that
// the runner skips that server.
"bun": `#!/bin/sh
case "$1" in
--print) echo "$0" ;;
*/coordinator.ts) printf '%s\\n' "$@" > "$0.coordinator" && echo COORDINATOR_READY && exec cat > /dev/null ;;
install) exit 1 ;;
test) for arg; do file=$arg; done; echo "$file" >> "$0.tests" ;;
esac
`,
});
const bin = String(dir);
for (const name of ["docker", "who", "bun"]) chmodSync(join(bin, name), 0o755);
// The job this test runs in can be a Buildkite job. The runner under test is not one.
const env = Object.fromEntries(
Object.entries(bunEnv).filter(([name]) => !/^(BUILDKITE|GITHUB_|EXPECTED_PLATFORM_)/.test(name)),
);
await using proc = Bun.spawn({
cmd: [
nodeExe()!,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) Contributors on Linux whose node predates default type stripping get two failing tests from bun test test/internal/docker-services.test.ts instead of a skip. test/internal/docker-services.test.ts:322 gates the runner suite only on !nodeExe(), and test/internal/docker-services.test.ts:349 then runs that node on scripts/runner.node.ts, a TypeScript file; Node 22.x below 22.18 and anything older exits with ERR_UNKNOWN_FILE_EXTENSION and the exit object mismatches. Fix: skip the suite when the found node cannot load a .ts module (probe node --print on a tiny .ts fixture or check process.versions.node against the strip-types floor), or run the runner under the same mechanism CI uses.

Why this was flagged

The suite at test/internal/docker-services.test.ts:322 runs whenever isLinux and nodeExe() returns a path. runRunner at :347-362 spawns that node on join(testsDir, "..", "scripts", "runner.node.ts"). Node loads a .ts entry only with type stripping, which is on by default from 22.18 and 23.6; on an older node the process exits 1 with ERR_UNKNOWN_FILE_EXTENSION before writing bun.coordinator or bun.tests, so :384 and :392 compare {coordinatorArgv: [], ran: [], exit: {exitCode: 1, ...}} against the expected objects and fail. Population: local contributors on Linux distributions that ship Node 18/20/22-LTS-early (Debian/Ubuntu system packages), which is the common case outside the CI images that pin 26.3.0 (scripts/build/ci-images/spec.ts:102). The base has no such test. REVIEW.md requires a skipIf when a system dependency is unavailable; here the dependency is 'a node that strips types', and the guard only checks presence. Remedy: extend the skip condition to the capability, not the binary.

Verification: Triggers when a Linux contributor's node on PATH predates default type stripping. test/internal/docker-services.test.ts:322 gates the suite only on !nodeExe(), which has no version check. Line 349 then spawns nodeExe()! on scripts/runner.node.ts without --experimental-strip-types. Such a node exits 1 with ERR_UNKNOWN_FILE_EXTENSION, so both toEqual assertions at 384 and 392 fail rather than skip.

let services: ServiceName[] | undefined;
for (const specifier of importSpecifiers(source)) {
if (!specifier.startsWith("./") && !specifier.startsWith("../")) continue;
const imported = posix.join(posix.dirname(path), specifier).replace(/\.[cm]?[jt]sx?$/, "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) A valkey test that imports its helper through the js/* tsconfig alias is not docker-backed for the runner, so its redis container is not prestarted and the file waits for a cold start inside itself, or inside the parallel batch once the generator allowlists it. scripts/docker-services.ts:60 skips every specifier that does not begin with ./ or ../, but test/tsconfig.json defines "js/*": ["./js/*"] and bun resolves import ... from "js/valkey/test-utils" the same as the relative form. Fix: resolve helper imports through every spelling bun accepts, at least the tsconfig paths aliases, or match on the helper basename plus test-utils path suffix rather than relative-only specifiers. [also at: scripts/docker-services.ts:42 - nit: a test that imports the docker helper with a spelling other than docker/index or docker/index.ts is not seen as docker-backed, so it can enter the parallel batch. importsDockerIndex at scripts/docker-services.ts:42 accepts only (?:\.ts)?, while the helper lookup at line 60 strips any \.[cm]?[jt]sx?, and the bare directory import ../../docker that Bun resolves to index.ts matches neither.]

Why this was flagged

A test under test/js/valkey/ that writes import { createClient } from "js/valkey/test-utils" (the js/* alias is declared in test/tsconfig.json paths) reaches scripts/runner.node.ts:573 through needsDockerService. servicesOfImportedHelpers at scripts/docker-services.ts:60 drops the specifier because it starts with neither ./ nor ../, so requestsIn returns backed=false. On the base branch the js/valkey/ prefix row in test/docker/prestart-map.mjs made every file under that directory docker-backed regardless of import spelling, prestarted redis_unified and kept the file out of the bucket. After this change the file is sorted among non-docker files, redis_unified is not on the coordinator's argv (runner.node.ts:744), and the file pays the cold compose up wait in its own beforeAll; after the next scripts/update-parallel-allowlist.mjs run it can be allowlisted and wait inside the bun test --parallel batch. Remedy: resolve tsconfig path aliases (or match the helper by path suffix) in servicesOfImportedHelpers.

Verification: nit — triggers only when a future test file imports the valkey helper through the js/* tsconfig alias; no such file exists in the tree today. scripts/docker-services.ts:59 discards any non-relative specifier before the helperServices lookup at :60-63. On the base branch the js/valkey/ prefix row in test/docker/prestart-map.mjs covered any file under js/valkey/ regardless of import spelling.

Comment on lines +259 to +261
// Only a debug build needs more than the default: there the coordinator takes several seconds to start.
// No CI lane runs one.
const timeout = isDebug ? 30_000 : undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit (optional): the three coordinator tests pass a per-test timeout, which test/CLAUDE.md forbids ("Do not set a timeout on tests. Bun already has timeouts"). test/internal/docker-services.test.ts:261 sets timeout = isDebug ? 30_000 : undefined and hands it to test.concurrent at lines 281, 294 and 317. Fix: drop the timeout argument and the timeout constant; if a debug build is genuinely too slow for the default, shrink the work the test does (CI already passes its own --timeout via scripts/runner.node.ts:2172) rather than widening the ceiling.

Why this was flagged

test/internal/docker-services.test.ts:261 defines const timeout = isDebug ? 30_000 : undefined; and passes it as the third argument of test.concurrent at lines 281, 294 and 317. test/CLAUDE.md line 120 says in a CRITICAL rule not to set a timeout on tests, and the root CLAUDE.md line 105 says a per-test timeout is only for a rare outlier. The base branch has no such test file, so the convention slip is introduced here. A maintainer reading the file sees a hand-set 30 s ceiling on three subprocess tests, which is the pattern the repo rules reject; no safeguard catches it because no lint checks test timeouts.

Verification: test/internal/docker-services.test.ts:261 const timeout = isDebug ? 30_000 : undefined; is passed as the third argument to test.concurrent at lines 281, 294 and 317. test/CLAUDE.md:120 states "CRITICAL: Do not set a timeout on tests. Bun already has timeouts.". Triggering condition: any debug-build local run of this file; the base branch has no such file, so the slip is new.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant