Files
command-center/agent/managed-skills/fixture-delay-window-race-test/SKILL.md
T

2.8 KiB

name, description
name description
fixture-delay-window-race-test Pin a race/abort bug in a fixture-server test by delaying only the Nth request so the interruption lands inside the in-flight window; assert via a driver-installed unhandledRejection reporter.

Reproducing a timing-window bug against a fixture server

Use when a defect only appears in a window — a promise created before it is awaited, a kill/abort landing mid-flight, a prefetch interrupted — and a fixture HTTP server answers instantly, so the window never opens. Earned in dankvoice issue #7 ([Unhandled Rejection] AbortError from /say stop during a pipelined synthesis).

Why naive attempts pass vacuously

  1. Uniform delay on the fixture route (DV_LATENCY_MS for every request) makes all requests resolve at the same time, so by the time the interruption fires nothing is in flight. The test goes green against the broken code — worse than no test.
  2. Asserting on a log line count that also counts interruption noise: dankvoice's driver prints PLAYS <n> as the number of player log lines, and the stub appends KILLED on SIGTERM — so a single playback with one interrupt reports PLAYS 2. Count the payload lines instead (grep -c 'PLAYER .*\.wav$').

The shape that works

  • Delay only requests after the first. The first response gets the pipeline into playback; the second stays in flight, which is the window:
    const spoken = received.filter((c) => c.url?.endsWith("/audio/speech")).length; // already pushed
    const delay = spoken > 1 ? Number(process.env.DV_SYNTH_MS ?? 0) : 0;
    if (delay > 0) setTimeout(answer, delay); else answer();
    
  • Make the interruption land inside it. Player holds long (DV_HOLD=2), interruption at a fixed offset (DV_STOP_AT=400) with the delayed response far later (DV_SYNTH_MS=1200): margins wide enough that the abort is neither before the request nor after the response.
  • Turn unhandled rejections into observable output in the test driver instead of letting the runtime crash on them — that is also exactly what the real harness does to the user:
    process.on("unhandledRejection", (reason) => {
      console.log(`UNHANDLED ${reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason)}`);
    });
    
    Then assert [[ "$output" != *"UNHANDLED"* ]] plus the concrete behavior (segments actually played, player killed).
  • Leak checks measure a delta, not an absolute: count dankvoice-* temp dirs before and after the run and compare; an absolute == 0 fails on another test's leftovers.

Order

Write the scenario first and watch it fail on the rejection assertion specifically — if it fails on some other assertion (a count, a kill line), the window is wrong, not the bug. Then fix, then confirm red→green on that one assertion before running the full suite.