Repository navigation
test(mcpl): the request-timeout tests decide when a late reply comes, instead of sleeping for it - #482
Merged
nissa-seru merged 3 commits intoOct 18, 2026
Conversation
… instead of sleeping for it Three tests in test/mcpl-request-timeout.test.ts slept a fixed time for a stub server's late tools/call reply: 350 ms for a reply at 300 ms, and 600 ms for replies at 500 ms. On a loaded runner the reply lands after the sleep. CI for cabf3d0 failed that way on macOS with Node 24, in "a mandatory call deadline overrides requestTimeoutMs: 0 and surfaces its late response" (anima-research#478). On main at e670363, with the reply moved past the sleep, that test and "a late STATEFUL orphaned response is surfaced" see no orphaned-response, and "a timed-out request that later receives a response does not crash" passes even when the reply comes after it. The stub now holds the tools/call reply until the host's next tools/list. After the rejection, each of the three tests calls sendToolsList(). The stub writes the held reply ahead of the list's own on the same stdout, and the connection handles lines in order, so the late reply has been handled by the time the list resolves. The reply can't arrive before the timeout either. Each of the three has a 30 s timeout of its own, since a timeout that never fires now leaves the call pending. "requestTimeoutMs: 0 disables the per-request watchdog" waited 300 ms, but the default timeout is 60 s, so it passed just the same when 0 fell back to the default. It now ticks a mocked setTimeout past 60 s and checks that the call is still pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… checks its late reply was sent From our internal review of the previous commit: - The plain and stateful tests' connection times out at 100 ms, and the tools/list that releases the held reply inherited that timeout, so a slow round trip could still fail them. After the rejection, both now lift the connection's request timeout, a private field sendRequest reads for each request. - The stub's tools/list answer says, in its tool's description, when it wrote a held reply first. Each of the three tests checks it, so a test whose stub held nothing, such as after the comment its replace() looks for changed, fails instead of passing without a late reply. - The plain test checks that its late reply emitted no orphaned-response. - The watchdog test ticks past 2^31 - 1 ms, the longest delay setTimeout takes, so any finite watchdog that 0 armed would fire, and restores the real timers in a finally before its assertion. - The comments say the next tools/list releases the held reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: liftRequestTimeout sets the connection's private requestTimeoutMs through a cast. If the field were renamed, the cast would set a property nothing reads, and the plain and stateful tests would race a 100 ms watchdog again, which shows only as a flake. The helper now asserts the field is an own property of the connection first, so a rename fails both tests on every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Required label not found on this PR. |
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three tests in
test/mcpl-request-timeout.test.tsslept a fixed time for a stub server's late reply, so a loaded runner could fail them. The stub now holds the reply until the test's nexttools/list, so the test decides when the late reply comes. A fourth test in the file, which passed whether or notrequestTimeoutMs: 0disabled the watchdog, now checks past any delay a watchdog could have.!Fixes #478
Problem
cabf3d0failed it on macOS with Node 24, withactual: [](Flaky test: "a mandatory call deadline overrides requestTimeoutMs: 0" races a fixed sleep against a child's 300 ms reply #478).mainate670363(Linux, Node 24.22.0), we ran the file's three tests that sleep for a late reply against the builtMcplServerConnection, with the reply moved past each test's sleep:orphaned-response;tools/liston a connection whoserequestTimeoutMsis 100, so that call can time out on a slow runner as well.DEFAULT_REQUEST_TIMEOUT_MS), so the test passes just the same when0falls back to the default. With the??where the connection reads the setting changed to||, it still passes.Changes
tools/callreply. Its nexttools/listanswer writes the held reply first, and then says so in the tool's description.sendToolsList()after the rejection. The connection handles the stub's lines in order, so by the time the list resolves, the late reply has been handled: surfaced asorphaned-response, or ignored. Each test checks that the list's answer says a held reply went first. The reply can't arrive before the timeout either, since the stub sends it only when the test asks.tools/listhas no watchdog of its own.orphaned-response.setTimeoutpast 2^31 - 1 ms, the longest delaysetTimeouttakes, and checks that the call is still pending. It restores the real timers in afinally, before its assertion.What each part is for, and what we left out
tools/list. It makes the order certain, so a runner's speed can't decide any of the three verdicts. It's aheldvariable, anidparameter on the stub'sreplyhelper, and the line in itstools/listbranch that writes the held reply first, which do nothing for the tests that don't hold a reply. Flaky test: "a mandatory call deadline overrides requestTimeoutMs: 0" races a fixed sleep against a child's 300 ms reply #478 suggests waiting for theorphaned-responseevent, raced against a bound of several seconds. That fixes the sleep in two of the tests, but the plain test has no event to wait for, and the deadline test's 75 ms deadline would still have to fire before its 300 ms reply arrived.replace()looks for changed, the test fails. It's a constant, two lines in the stub'stools/listbranch, and one assertion in each test.requestTimeoutMsfor each request, andsendToolsList()takes no timeout of its own. The test sets that private field to 0, as several of this repository's tests reach internals, and first checks that the field is there, so a rename fails the test instead of leaving the lift setting nothing. A larger timeout for the whole connection would make each timeout test wait that long, and would still be a bound a slow runner could pass.Tests
mainit takes about 3 s (Linux, Node 24.22.0).src/mcpl/server-connection.ts:orphaned-response: the stateful and deadline tests fail;sendToolsCallWithDeadlinepassing no deadline: the deadline test fails, at its 30 s timeout;requestTimeoutMs: 0falling back to the default: the watchdog test fails. Onmain's test file it passes.replace()looks for changed, so no test holds a reply: the three tests fail;tools/list300 ms late: all seven pass. Without the lift, the plain and stateful tests fail;server-connection.ts: the plain and stateful tests fail.mainat7706636with the open pull requests that touchserver-connection.ts,transport.tsor this test file and merge cleanly with it (fix(code-execution): the python runner gets the child allowlist and its own working directory (follow-up to #175) #193 and fix(mcpl): contain inbound request handler failures #208 together, feat(awareness): make Discord awareness marks an explicit, one-shot choice that never gates MCPL #250, fix(channels): the host posts nothing into a channel its server declared inbound-only #350), the file passes 7 of 7 in each tree, and each tree typechecks.Overlapping pull requests
protocolVersionto the stub'sinitializereply, the line between the two stub lines this PR changes: thereplyhelper and thetools/listbranch. Each conflicts with this PR there, and keeping both changes resolves it. Both already conflict withmainelsewhere.Not verified
actual: [], and our probe shows the same result when the reply comes after the sleep.Out of scope
Our internal review found four places in
server-connection.tsthat no test in this file reaches. This PR leaves them as they are:requestTimeoutMsfallback in the stub used after a failed first connect;checkpoint;sendToolsCallWithDeadline's handling of a deadline that isn't a positive number.Written by Abigail-2459 (Claude Opus 5.5), contributing through nissa-seru. Ada-1017 (Claude Opus 5.5) agreed the design. Alfred-2451 (Claude Opus 5.5) reviewed the change, and it had an internal review before publication.