From 1da50fe29acf5ed4d2c289323d9d315a1ca5c0dd Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sun, 20 Sep 2026 05:29:12 -0400 Subject: [PATCH 1/2] fix(server): stop Claude probe stubs from outliving the test run The probe aborts the SDK without awaiting the child, so a stub held open by an unconditional interval is reparented to init and survives until reboot, accumulating one stranded process per run. Signed-off-by: Yordis Prieto --- .../src/provider/Layers/ClaudeCapabilitiesProbe.test.ts | 9 ++++++++- .../provider/Layers/ProviderInstanceRegistryLive.test.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeCapabilitiesProbe.test.ts b/apps/server/src/provider/Layers/ClaudeCapabilitiesProbe.test.ts index 271dadae629f..0e7c08ce2c81 100644 --- a/apps/server/src/provider/Layers/ClaudeCapabilitiesProbe.test.ts +++ b/apps/server/src/provider/Layers/ClaudeCapabilitiesProbe.test.ts @@ -129,7 +129,14 @@ it.layer(NodeServices.layer)("Claude capability probe SDK boundary", (it) => { " });", " }", "});", - "setInterval(() => {}, 1_000);", + "// Stay alive for follow-up control requests, but never outlive the", + "// parent: the probe aborts the SDK without awaiting the child, so an", + "// unconditional interval would strand this process until reboot.", + "const keepAlive = setInterval(() => {}, 1_000);", + 'lines.on("close", () => {', + " clearInterval(keepAlive);", + " process.exit(0);", + "});", "", ].join("\n"), ); diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts index a2c8d6446a73..3e54c4bcde2f 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts @@ -207,7 +207,14 @@ const makeTildeProviderFixtures = Effect.fn( " },", ' }) + "\\n");', "});", - "setInterval(() => {}, 1_000);", + "// Stay alive for follow-up control requests, but never outlive the", + "// parent: the probe aborts the SDK without awaiting the child, so an", + "// unconditional interval would strand this process until reboot.", + "const keepAlive = setInterval(() => {}, 1_000);", + 'lines.on("close", () => {', + " clearInterval(keepAlive);", + " process.exit(0);", + "});", "", ].join("\n"), ); From bed08e08b3e8b6e4416ab85c8d3e43866d9e645a Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sun, 20 Sep 2026 05:32:48 -0400 Subject: [PATCH 2/2] docs(fork): record the stranded test process divergence The ledger is the only place a divergence living entirely in test fixtures is visible, since a sync that reverts it fails nothing. Signed-off-by: Yordis Prieto --- ...5-a-test-run-leaves-no-processes-behind.md | 47 +++++++++++++++++++ docs/fork/README.md | 2 + 2 files changed, 49 insertions(+) create mode 100644 docs/fork/0025-a-test-run-leaves-no-processes-behind.md diff --git a/docs/fork/0025-a-test-run-leaves-no-processes-behind.md b/docs/fork/0025-a-test-run-leaves-no-processes-behind.md new file mode 100644 index 000000000000..7d348f5e0b60 --- /dev/null +++ b/docs/fork/0025-a-test-run-leaves-no-processes-behind.md @@ -0,0 +1,47 @@ +# 0025: A test run leaves no processes behind + +- PR: [TrogonStack/t3code#57](https://github.com/TrogonStack/t3code/pull/57) +- Status: active + +## What you can do now + +- Run the suite as often as you like without the machine accumulating + stranded processes. What the tests start, the tests take with them when + they go. +- Trust your process list again. A stray provider process in it now means + something is genuinely running, not that you ran the tests last Tuesday. +- Leave a long-lived machine running the suite on a loop without it slowly + filling with residue that only a reboot clears. + +## Why + +The leak was silent in the way that matters: nothing failed, nothing was +logged, and every run reported green. The cost accrued outside the test +report entirely, one process per run, each holding its memory and its file +handles for as long as the machine stayed up. Found in the wild, the oldest +survivors were days old and their temporary directories had long since been +deleted out from under them. + +Silence is what makes it worth fixing rather than living with. A test that +fails gets attention on the spot. A test that quietly leaves something +behind gets attention weeks later, from whoever is wondering why a +workstation is sluggish, and by then the connection back to the suite is +gone. The processes are idle, so nothing in the usual places points at them. + +It also erodes a thing the suite is supposed to be good for. Some of these +tests exist to check that provider processes are started and cleaned up +correctly, and a harness that strands its own children is poorly placed to +make claims about cleanup. + +## Upstream considerations + +A clean upstream submission. This carries no fork-specific intent, fixes +upstream's own tests, and changes no product behavior, so there is nothing +here upstream would want to weigh. Once an equivalent lands there, this +entry goes. + +Worth flagging for a sync rather than a rebase: the divergence lives +entirely in test fixtures, so a sync that takes upstream's copy of either +file reintroduces the leak without anything going red. The suite passes +either way, and the symptom shows up only in the process list on whatever +machine ran it. diff --git a/docs/fork/README.md b/docs/fork/README.md index b5a63efb3c92..c288f2a2d6bd 100644 --- a/docs/fork/README.md +++ b/docs/fork/README.md @@ -59,3 +59,5 @@ Each entry uses these sections: active, [#36](https://github.com/TrogonStack/t3code/pull/36) - **0024** [A refused merge says why, and an administrator can merge anyway](./0024-a-refused-merge-says-why.md) active, [#38](https://github.com/TrogonStack/t3code/pull/38) +- **0025** [A test run leaves no processes behind](./0025-a-test-run-leaves-no-processes-behind.md) + active, [#57](https://github.com/TrogonStack/t3code/pull/57)