Skip to content

[Bug]: the native acceptance teardown still times out on Windows — the host stalls inside the service close #30

Open

Description

@modacker

What happens

The native-terminal acceptance on the Windows runner fails intermittently in its teardown. The test itself passes; the file's afterAll hook times out waiting for the native host to say it closed.

This is not the failure that #17 fixed. #17 moved the host's exit into the process.send callback, so the host goes once it has reported — that covered the five-second drag that came after the report (#22). Here the host never reaches the report at all: it stalls inside the service close, one step earlier.

How it shows up

1) packages/webui/test/browser/native-runtime.spec.mjs:94:1 › V04/V06: native PTYs survive another page, reload and a full five-slot roster
  "afterAll" hook timeout of 30000ms exceeded.
  > 43 | test.afterAll(async () => {
  Error: the native host did not finish closing

The Windows job reports 484 passed / 1 failed.

Evidence

  • Run 42953031674, Windows job attempt 1 (job 130301566263): failed as above.
  • The same commit, re-run as attempt 2 (job 130313055925): 485 passed / 0 failed.
  • The test body took 6.8 s in the failing run and 6.4 s in the passing one — the same order of magnitude as the runs that always passed (6.4 s to 7.9 s), so the body is not what failed.
  • Earlier Windows runs (42906391703, 42856963545, 42849887547) all passed this file.

Where it stops

Since #17 the host reports each step of its own shutdown on stderr, so a shutdown that does not finish says which step stopped answering. The failing run's last line is:

[native-host] service.close

It never reaches terminals exited. The runtime's own shutdown lines (Turn controller shutdown, All MCP connections closed) follow, and then nothing.

The host's shutdown order is service.close() → terminals exited → apiHost.close() → terminals checked → data dir removed → reported. So the stall is inside service.close().

WebuiService.close() runs the owners in order and awaits each: terminal, websocket-connections, websocket, browser-bridge, miniapp-proxies, http-listener, then the harness port. The runtime logs its own shutdown inside that sequence, so the stall is at or after the harness port close.

What is known and what is not

Known: the body passes, the failure is the teardown, it is not reproducible on demand, the host stalls inside the service close rather than after it, and it is not the mechanism #22 describes.

Not known: which owner inside the close sequence stalls on the runner, and whether it is the port close itself or something the port close waits for.

Why it matters

A flake in a gate is a gate nobody trusts. This one lands on the Windows runner, so it will arrive on any PR that happens to run there, dressed as a failure of the thing being changed.

Where it should be fixed

The service close has no deadline: every owner is awaited indefinitely, and one owner that never answers holds the whole shutdown. Two things would make the next occurrence both faster to diagnose and survivable:

  • bound each owner's close, so a stalled owner is named instead of hanging the shutdown;
  • have the host report the step it reached even when the service close does not finish, so the trace survives the stall.

Both live in packages/webui, so they can be fixed in this repository. The node-pty drag in #22 cannot be fixed from here — that timer is internal to node-pty.

Notes

  • This file does not exist upstream: MiniMax-AI/minimax-code has no packages/webui directory at all, so there is nowhere upstream to report this.
  • The change under review when this was seen was PR feat(webui): ship the wordmark's own font with the client #29, which does not touch this path. native-runtime.spec.mjs imports only test from the harness and never calls openApp, and the harness change in that PR is entirely inside openApp.

Activity

  1. modacker commented on Nov 11, 2026

    @modacker
    CollaboratorAuthor

    More evidence, and the upstream attribution

    Three things are worth adding to this issue, all checked against the logs.

    1. The node-pty upstream already carries this

    The symptom is node-pty's, and it is already recorded there:

    • microsoft/node-pty#952 — ConPTY kill(): the console-list agent races the pseudoconsole teardown, so the leftover-process sweep never runs — the same 5000 ms fallback (resolve([this._innerPid])).
    • microsoft/node-pty#1007 — conpty_console_list_agent crashes with "AttachConsole failed" — and it says, in as many words, that the pty's exit event is never delivered and the child never finishes, so the test runner waits forever.
    • microsoft/node-pty#974 — ConoutConnection worker and conpty_console_list_agent inherit process.execArgv, so the host's --require preload runs inside them.

    No new upstream issue is needed for any of these.

    2. This repository's own preload reaches node-pty's helper

    The acceptance host forks itself with the tsx loader in execArgv:

    packages/webui/test/browser/native-runtime.spec.mjs:13
    execArgv: ["--import", pathToFileURL(path.join(root, "packages/webui/node_modules/tsx/dist/loader.mjs")).href]
    

    node-pty forks its console-list helper with child_process.fork, which inherits process.execArgv — so the helper runs under tsx. The failing run's host log shows exactly that:

    node_modules\node-pty\src\conpty_console_list_agent.ts:13
    Error: AttachConsole failed
        at Object.<anonymous> (node_modules\node-pty\src\conpty_console_list_agent.ts:13:28)
        at Object.transformer (packages\webui\node_modules\tsx\dist\register-OY9cqBEN.cjs:9:3619)
    

    Two such crashes are in the host's captured log. That is the shape of #974, and it is specific to this repository: the preload is ours.

    3. Two corrections to the numbers above

    Where this must not go

    packages/webui/docs/agents/issue-tracker.md is explicit: "Nothing in this repository may be published to upstream: upstream synchronization runs by three-way merge, so anything pushed or filed there shows up as unreviewed content in the public projection." The upstream for a webui defect is not MiniMax-AI/minimax-code — that repository has no packages/webui at all. It is microsoft/node-pty, and the issues are already open there.

    The fix, when it lands here

    Two places, both inside packages/webui, and both in the shape PR #17 established — measure, fix the cause, and leave the trace saying which step stalled:

    • packages/webui/src/server/service.ts:352-377 — #closeOwners() awaits every owner with no deadline. One owner that never answers holds the whole shutdown.
    • packages/webui/test/browser/support/native-browser-host.ts:65-97 — the host should report the step it reached even when service.close() does not finish, so the next occurrence says more than a timeout does.

    Both are in this repository and neither needs anything from upstream to land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions