Skip to content

fix(tests): drive runtime deadlines from injected clocks instead of wall time - #434

Merged
ScriptedAlchemy merged 2 commits into
mainfrom
fix/sweep-deterministic-runtime-tests
Sep 3, 2026
Merged

ScriptedAlchemy merged 2 commits into
mainfrom
fix/sweep-deterministic-runtime-tests

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Four wall-clock-bound tests wobbled under load. Each is fixed at its source (an injected clock, an injected timer seam, an event-ordered wait, or a cheaper in-process path) — no retries, no padding, no longer timeouts.

Item Test Root cause Fix
a rsc-runtime/tests/dispatcher.test.ts › fails a permanently pending boundary when the elapsed deadline expires The 150 ms maxElapsedMs deadline ran on Date.now() + the real Effect clock, starting before the Flight worker was spawned. Under load the worker's shell arrived after 150 ms and the shell emit itself threw elapsed-time-exceeded (expected a shell event). The test also carried { retry: 2 }. createAgentRenderEventSequence accepts a now time source; createAgentRenderEventSession accepts an Effect Clock and provides it to the stream, so the elapsed check and the pending-boundary Effect.sleep run on one clock. The test is it.effect with TestClock: the shell may take any real time, TestClock.adjust(150) expires the deadline whether the sleep is already armed (it fires) or armed later (no time remains). Retry removed.
b agent-bundle/tests/mcp-probe-service.test.ts › returns a timed-out report without awaiting stalled teardown (+ its budget-spent sibling) Real 10 ms budget timer + real 50 ms teardown wait + 250 ms stalled closes, guarded by a real 150 ms race — a busy event loop pushes the report past the guard. McpProbeService gains a timers seam (McpProbeTimers.schedule(callback, delayMs, { unref })) behind the budget timeout, teardown wait, plugin-data cap, and removal retry; production keeps Node timers (unref preserved). The tests inject a manual scheduler, fire(10) / fire(mcpProbeTeardownWaitMs) in event order, assert the report settled before the next macrotask while both closes hang forever, then fire the cap and settle() so the plugin-data directory is removed.
c agent-bundle/tests/route-unit/lifecycle-replay.test.ts (5 s timeout) Every replay() without an injected renderer forks a Node child (--conditions=react-server --import jiti-register) that jiti-transpiles the whole render graph; ~1.2–1.5 s per fork on this machine, > 5 s for the two-fixture test under load. The two 5 s tests pass render: renderRouteEvents: the route-unit pool already runs under react-server, so the route renders in-process through the same renderer the child uses (433 ms and 38 ms instead of > 5 s). The child-fork path stays covered by the captured-fixture tests, which keep their existing budgets.
d agent-bundle/tests/playground-service.test.ts › sets an atomic subscription replay boundary … Investigated the suspected registration/publication race: the replay boundary (latest = nextSequence - 1, backlog filter, subscriber registration, first drain) is computed inside the session's serial queue, and the reentrant append issued from onEvent is enqueued behind that same task, so the boundary is already atomic — no source change. The observed [1] vs [1, 2] is the eventually helper's fixed budget (100 × 1 ms polls) racing an fsync-backed append under I/O load. The test now waits on deferreds resolved from inside the subscriber, awaits the reentrant append (sequence: 2), and asserts fail-closed right after the overflowing append resolves (publish runs synchronously inside append). No polling budget remains in the test.

Evidence

Runs in the worktree on a loaded machine (other lanes + pnpm test:unit running concurrently); loop logs under /tmp/final-sweep/a3a-*.log.

Item Before After
a 19/20 (1 × Agent render elapsed time exceeds 150ms, retry disabled for the baseline) 20/20 + 20/20 under full-suite load; the deadline wait spends ~35 ms real time (no 150 ms sleep)
b 20/20 and 18/18 — not reproduced locally in 38 clean runs (root cause is structural: two real timers vs a 150 ms guard) 20/20 + 20/20 under full-suite load
c 4/5 (1 × test timed out in 5000ms, first test) 20/20 (file 12–15 s, dominated by the two child-path tests)
d 40/40 — not reproduced locally 20/20 + 20/20 under full-suite load

Gates: pnpm typecheck ✅, pnpm lint ✅, pnpm test:route-unit ✅ (44/44), pnpm test:unit 3002/3003 — the one failure is native-claude-contract.test.ts › fails closed when the candidate plugin… timing out at 5 s; it fails identically 3/3 on a pristine origin/main worktree on this machine (~4.95 s of runNativeClaudeSmoke work per test) and is unrelated to this branch.

Test plan

  • pnpm exec rstest --config rstest.unit.config.ts packages/rsc-runtime/tests/dispatcher.test.ts (32/32)
  • pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/mcp-probe-service.test.ts packages/agent-bundle/tests/playground-service.test.ts (82/82)
  • pnpm exec rstest --config rstest.route-unit.config.ts packages/agent-bundle/tests/route-unit/lifecycle-replay.test.ts (5/5)
  • 20× loops per item under load (table above)
  • pnpm typecheck, pnpm lint, pnpm test:route-unit
  • CI green

Review status

  • chatgpt-codex-connector answered "You have reached your Codex usage limits for code reviews" to both the automatic trigger and the explicit @codex review on head 72da724.
  • Last codex-reviewed SHA: none. Unreviewed SHAs: 071af8794 (fix), 72da72439 (changeset PR reference).
  • Per the review-quota fallback, merging on CI green; review threads left after merge will be answered in a follow-up PR.

…all time

- rsc-runtime: createAgentRenderEventSequence accepts a time source and the
  render session accepts an Effect Clock so the maxElapsedMs deadline (elapsed
  check + pending-boundary sleep) runs on one injectable clock; the dispatcher
  deadline test now uses TestClock and no longer retries.
- agent-bundle: McpProbeService gains a timers seam behind its budget timeout,
  bounded teardown wait, plugin-data cap, and removal retry; the stalled
  teardown tests fire those timers in event order.
- lifecycle-replay route-unit tests render in-process where the pool already
  runs under react-server instead of forking a jiti child per replay.
- playground-service replay-boundary test waits on subscriber-driven deferreds
  and awaited appends instead of a fixed polling budget.
@changeset-bot

changeset-bot Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 72da724

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@agent-bundle/runtime Patch
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@434
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@434
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@434

commit: 72da724

@ScriptedAlchemy
ScriptedAlchemy merged commit 43abb2d into main Sep 3, 2026
13 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fix/sweep-deterministic-runtime-tests branch September 3, 2026 17:19
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