Repository navigation
test(mcpl): the written-to test's host write fails before the child's SIGTERM, in both engines - #896
Merged
Conversation
… SIGTERM, in both engines The test of a server that dies by SIGTERM after the host's write to it failed needs the host to have seen its write fail before it handles the child's exit: the close's reason names the pipe only if the pipe error came first. Its children killed themselves as they were written to, the legacy one once its burst of pings was out and the modern one as soon as its stdin brought data, which left that order to the host's event loop. Under load the loop could write everything before the child died, or take the exit first, so that nothing it wrote had failed, and the modern case read writeFailed false. Both children now close their stdin on SIGUSR1, as the pipe-failure test's child does (DEAF_ON_USR1), so the host's next write fails while the child still runs. The test waits until the host has recorded that failure (pipeFailureOf), and only then sends the SIGTERM. So the pipe error comes first whatever the load. The child's exit then has to be handled within the 2 s the transport gives a child after a pipe failure before ending it itself; if it isn't, the transport ends the child and the test fails with an alert. The test's title said the child died while the host was writing to it, the race this removes. It now says the SIGTERM comes after the host's write failed. A SIGTERM with no failed write is the case of the test of a child that ends as asked.
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 29, 2026
main 7aab5e3 brings anima-research#896 (the written-to test's host write fails before the child's SIGTERM, in both engines), anima-research#871, anima-research#897, anima-research#893, anima-research#870 and anima-research#869 since 0f11975. No file conflicts.
1 task done
tel-0s
added a commit
that referenced
this pull request
Oct 29, 2026
…'s context retired, MCPL inference aborted at stop, the re-armed deadline cut, failure accounting released with its agent main at 612f6fd is eight merges past 4a45766, with no release among them, so package.json stays 0.30.0 and this branch's floors stand (chronicle ^0.6.0, context-manager ^0.25.0, membrane ^0.8.0), which test/dependency-ranges pins. It brings #871 (a re-armed turn's deadline cut follows the deadline-cut rule; an ended agent takes its re-armed deadline cut with it), #896 (the race order of the MCPL down alerts), #897 (an ended agent's failure accounting goes with it: forgetFailureAccounting), #893 (the [inference-failed] marker's pins), #870 (strict credential mode for opt-in WebSocket dials), #895 (stop() aborts each MCPL server's inference/request still running, and answers it -32000), #898 (a module's context is retired once its stop() completes: its pushes dropped with a line, its writes, calls and subscriptions refused, its unapplied response and unmerged gatherContext answer left out) and #884 (the suite's speed: setAbandonGraceForTests and CI). One path conflicted (3 regions): - src/module-registry.ts: - imports: main's boundedErrorMessage; heldWriteId stays out, as on this branch, which has no held-write handles. - the registry's constructor: both, this branch's window lookups (windowStatusOf, observes, viewKeeps, agentNameOf) and main's dropRetiredPush. - findMessageByExternalId's comment: this branch's (a '' names a write kept in memory, a surgery's hold or the wait for its agent's recovery; no held rows) with main's clause that a stopped module's context keeps its mapping as it was. The code merged cleanly: main's retired guards around the mapping's updates, without the held-row answer this branch removed. Read onto this branch past the conflict: - releaseAgentRuntime takes main's freshCallsCut.delete, forgetFailureAccounting and rearmDeadlineCuts.delete, beside this branch's forgetPresentation; an ephemeral run's disposal and disposeConversationAgent's name-only arm take them as main put them. - ModuleContext's doc (types/module.ts): a retired context's reads list this branch's window lookups (windowStatusOf, observes, viewKeeps, agentNameOf), which stay open as main's reads do; main's clause that a read meeting a held write may record its landing goes, as this branch has no held rows to meet. - applyProcessResponse is synchronous on main now; this branch's body has no await, so it takes main's form. storeSees: no new site; main opens and derives no context manager. agentSees: no new view decision (noteSpokenTo's existing check gains only the re-arm's reset). Notice tags: none new; main's new texts ([framework], [process] and [unstick] lines, the -32000 answer) go to stderr or an MCPL server, not to an agent. #898's retire and #884's suite changes touch no presentation point. Changelog: no release in main's range, so no fragment was consumed; main's five new fragments come in as written. retire-stopped-module-context's "reads answer, and fail, as before" covers this branch's window lookups. Tests adapted: - retired-module-context, the findMessageByExternalId case: its '' entry comes from a write a surgery's hold keeps in memory, released before the module's removal, where main made it with a quiesce. A write while quiesced is stored at once here (#291), so it mapped to its id. Tests: built with zero errors; no stale compiled test; fixtures copied. main's 35 changed test files: 1522 tests, 1516 pass, 1 fail (the retired-module-context case, adapted above; its file then 22 of 22), 5 skipped (Linux-only workspace cases). presentation*, window-*, dendrite-*, stored-injections, audience-stamp*, dependency-ranges, held-* and tune-out-* (44 files): 606 of 606. Full suite (npm test): 9518 tests, 9469 pass, 0 fail, 49 skipped (runtime-conditional: Linux-only rules, and temp directories that fold case or normalization). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
This makes the test "a server that dies by SIGTERM while the host is writing to it raises no alert, though the host's write failed, in either engine" put its two events in order itself: the host's write to the child fails first, and only then does the test send the child SIGTERM. Its new title names that order. Before, each child killed itself as the host wrote to it, which left that order to the host's event loop. In CI at #884's head, which merges
mainat0f11975e48, the test failed in its modern case. With this change, the test passed in each of 20 runs, and a red run shows it still fails when the transport alerts on such a close. The change is totest/mcpl-down-for-good.test.tsonly.Problem
The test came with #876. It needs the host to record its failed write before it handles the child's exit. At
0f11975e48,src/mcpl/transport.ts:413gives the close's reason as`child process exited, and its pipe failed with ${this.pipeError.message}`only whenthis.pipeErroris set, and'child process exited'otherwise.At
0f11975e48, the legacy child killed itself once its burst of pings was out, and the modern child as soon as its stdin brought data. In CI run 40826523575, at #884's heada3f568c96b, the Build & Test (ubuntu-latest, node 22.12.0) job failed this test. Its modern case readwriteFailed: false, withsignal: 'SIGTERM',alerts: 0andposted: 0, and its legacy case readwriteFailed: true.a3f568c96bdoesn't change this test's file. Insrc/mcpl/transport.tsit has the pipe-failure wait take its grace fromexitGrace(), which returnstestExitGraces.get(this.child) ?? STDIO_EXIT_GRACE_MS, and this test file doesn't callsetStdioExitGraceForTests, which setstestExitGraces. In a run at0f11975e48with the modern child's SIGTERM delayed 300 ms after its first stdin data, the test failed with the same reading.Changes
DEAF_ON_USR1, which the test "a connected server whose child the transport ends after a pipe failure is said to the operator once, though it dies by SIGTERM, in either engine" already uses. On SIGUSR1 that child runscloseSync(0)andopenSync("/dev/null", "r"), writesdeafto stderr, and writes apingrequest to stdout.deafline, which the child writes aftercloseSync(0). In the modern case it then makes one tool call, for the reason the comment aboveDEAF_ON_USR1gives: "A modern host drops the ping, so the test calls a tool instead." It waits until a new helper,pipeFailureOf(), reads the transport's recordedpipeFailure, and only then sends the child SIGTERM.pipeFailureOf()readspipeFailurefrom the legacy connection's transport, or from the modern connection's session'sstdio.STDIO_EXIT_GRACE_MSof the failure, or the transport ends the child itself and the close raises an alert.DIES_WHILE_WRITTEN_TO, the two children that killed themselves, is removed. The test's assertions are unchanged: in both engines,signal: 'SIGTERM',writeFailed: true, and no alert.On October 29 at 06:20 UTC, no open pull request changes
test/mcpl-down-for-good.test.ts.Tests
At a clean checkout of
eb88f49c69, on Linux with Node 24.22.1 and npm 11.19.1, afternpm installwithpackage-lock.jsonandnode_modulesremoved,npm run buildandnpm run pretest, with a one-minute load average of 11.5 at the start and 14.7 after the 20 runs below.npm ls --depth=0gives@animalabs/chronicle0.5.0,@animalabs/context-manager0.24.1,@animalabs/mcpl-core0.4.0,@animalabs/membrane0.8.0,@babel/parser7.29.11,@modelcontextprotocol/client2.4.0,@modelcontextprotocol/server2.4.0,@types/node22.20.5,@types/ws8.18.2,chokidar4.0.3,discord.js14.27.0,tsx4.23.15,typescript7.0.2 andws8.22.0.test/mcpl-down-for-good.test.ts: 20 of 20 pass.src/mcpl/transport.ts:414reading...(this.pipeError ? { pipeFailed: true } : {})in place of...(this.endedForPipe ? { pipeFailed: true } : {}): the test fails, readingwriteFailed: true,alerts: 1andposted: 1in both engines.On GitHub-hosted runners at Node 22.12.0, in a workflow outside this repository, with 3 CPU-burning processes beside the test on macOS and 2 on Ubuntu, this test alone passed 20 of 20 times on macOS and on Ubuntu with an earlier version of this change, whose test file differs from this head's only in comments and the test's title. At
0f11975e48it also passed 20 of 20 on both. So these runs show that version holding under that load, and don't show0f11975e48failing.Not verified
src/mcpl/transport.ts:395at0f11975e48callsthis.exitWithin(STDIO_EXIT_GRACE_MS), and:565sets that to 2,000 ms. The test sends its SIGTERM after it has polled for the recorded pipe failure. No run here measured how much of those 2 s the poll, the SIGTERM and the host's handling of the exit take, or slowed them past it. The red run shows that a close markedpipeFailedfails the test withalerts: 1.0f11975e48failing under load alone. No run showed it fail without the delay above. The CI failure was ata3f568c96b, not at0f11975e48.src/, and theno-changeloglabel applies.Amos-2081 made the change. Alfred-2451 ran the delayed run at
0f11975e48, and Alfred-2451 and Amos-2081 found that the failing case was the modern one. Naomi-2422 read the change. Daniel-2612 reported the failure in #884's CI, ran this change's tests at a clean checkout and wrote this description.