Repository navigation
Description
Context
packages/ui-server/tests/http-supported-mounts.test.ts "a dirty failed mount discards its resources before the next independent case" intermittently counts seven native-manager client sockets where it expects six (expect(sockets).toHaveLength(6), packages/ui-server/tests/http-supported-mounts.test.ts:286 on main @ 0760f7832).
Observed 2026-11-04:
- Hosted: CI run 41773863563, job
unit (3), for CONTRACT: feat(ui-server): admit generic chat files as durable server-owned originals #3342 at headd73637092bdd7a76ac43e07f2fd824405183f9ca(base main @0760f7832f3d81f897f7925b417e2a53dd2465b8):Expected length: 6 / Received length: 7at line 288 of that head. It was the only failure in the run. - Locally, running only this case (
bun test --timeout 30000 tests/http-supported-mounts.test.ts -t 'dirty failed mount') five times each: cleanorigin/main@0760f7832failed 1 of 5 runs with the same 7. The CONTRACT: feat(ui-server): admit generic chat files as durable server-owned originals #3342 head failed 1 of 5 in one batch and 2 of 5 in another.
The case opens six native.ready() calls and then counts the sockets in the fresh fixture's agent bucket. Something else in the fresh app is making a native call while those six connect and holds one more socket. #3342's commit 5e8939bce added await fresh.t.app.wsHost.journal?.maintain() before the six calls, on the theory that the turn journal's startup idle pass (#3386) was that caller. With that await in place, the case still fails, so the journal pass is not, or not the only, background caller.
#2894 (closed) was this case's earlier timing problem, which was six leftover idle timers after disposal. That is a different assertion and cause; its socket-key and join fix is still in place.
Scope
Find which native-manager calls a freshly prepared app makes in the background after prepare() returns. Record the calling site and when it runs relative to the six ready() calls in a failing invocation. Then make the case wait for that work, or count only the sockets its own six calls opened, whichever matches the case's intent: native shutdown cancels every socket idle deadline.
Out of scope
- Retries or raised deadlines.
- Loosening the assertion to "at least six".
- Changing the product's startup behaviour only to make the test pass. If the background call is itself a defect, file it separately.
Acceptance criteria
- The extra socket's caller is named, with a
file:linecitation, from a failing invocation that is captured on purpose (for example a forced delay). - The case passes 20 consecutive isolated runs on main, plus the full ui-server suite.
- A mutation receipt shows the case still fails if native shutdown leaves a socket idle deadline pending.
Verification
bun test --timeout 30000 packages/ui-server/tests/http-supported-mounts.test.ts -t 'dirty failed mount' repeated, then bun run test in packages/ui-server. This needs a host with unprivileged user namespaces for the rest of the file.
Contract impact
None expected; test fixture synchronization only.
Activity
schlessera commentedon Nov 4, 2026
Claiming (token
claim-3550-a7f3). Branch:test/3550-dirty-mount-seventh-socketfrom main @2fa67923d.Path collision noted: #3342 also edits
packages/ui-server/tests/http-supported-mounts.test.ts(it is the PR this issue unblocks); other reports are closure-level onui-server.schlessera commentedon Nov 4, 2026
Rate comparison, 2026-11-04: does #3342 or its commit
5e8939bceraise the rate?I ran only this case 20 times on each of three trees:
bun test --timeout 30000 tests/http-supported-mounts.test.ts -t 'dirty failed mount'inpackages/ui-server. The trees ran interleaved (main, head, head without the await, then repeat), one run at a time on one host, so all three saw the same load. Every run either passed or failed withExpected length: 6 / Received length: 7. No run failed in any other way.0760f7832f3d81f897f7925b417e2a53dd2465b8(#3342's base)d73637092bdd7a76ac43e07f2fd824405183f9ca(includes5e8939bce)5e8939bcereverted (noawait fresh.t.app.wsHost.journal?.maintain())Conclusion: neither #3342 nor
5e8939bceraises the rate. The three counts are within sampling noise at n = 20. The await before the sixready()calls does not measurably lower it either, so the turn journal's startup idle pass is at most one source of the seventh socket. Most of it comes from something else, and that something is present on plain main.The rate on this host today (about 50%) is much higher than in the first five-run samples (1 of 5 on main). Run-to-run load clearly matters, so compare arms only within one interleaved batch.
schlessera commentedon Nov 4, 2026
Status (2026-11-04): #3552 implements this and is ready at head
35313bdfa; it is not merged.packages/ui-server/src/brain/turn-journal.ts:352). That pass commits an observation, which hands the previous checkpoint to the observer's background release (packages/ui-server/src/brain/native-journal-observer.ts:313-323, viadeferReleasefromcommit). The release's nativereleaserequest was still holding a socket when the case's sixready()calls connected. Captured with a stack log on failing runs; details are in test(ui-server): join the journal's startup release before counting native sockets #3552. It is not a leak: the journal joins that release on close and exposesreleasesSettled().maintain()alone (the CONTRACT: feat(ui-server): admit generic chat files as durable server-owned originals #3342 approach) does not cover it.ready,maintain()andreleasesSettled()before counting. Before the fix 17 of 30 runs failed; after it 30 of 30 passed. Mutation receipts are in the PR.bun scripts/integrate.ts confirm 3552ran candidate proof (run 41780062866). The only failure was an unrelated test,ws-ask-turn-timeout.test.tsask_user_form(waitFor timed out), inunit (2). Under the strict flake rule a rerun cannot make proof green, so it is filed as ui-server: ask_user_form turn-limit case intermittently never sees its terminal frame #3553 (p1).bun scripts/integrate.ts confirm 3552 --issues schlessera/brain-kit#3550. Rebase onto main first if confirm asks for an exact-main candidate.test(ui-server): join the journal's startup release before counting n…
test(ui-server): join the journal's startup release before counting n…
Merge epic/3150-attachments (main b6ddb64) into PR #3342 after the #3550