Copilot CLI crashes on exit (Windows) — libuv `uv_async_send` on a closing handle (FAST_FAIL_FATAL_APP_EXIT)
- 主要語言
- Shell
- 星號
- 11.2k
- 分支
- 1.9k
- 平均合併
- 14 小時 16 分鐘
- 30 天內合併 PR
- 6
描述
### Describe the bug
On Windows x64, `copilot.exe` **consistently crashes at process exit** with a fatal
fail-fast (`0xc0000409`, subcode `0x7 FAST_FAIL_FATAL_APP_EXIT`). The session's work
completes normally; the crash happens only during teardown.
Live-debugger analysis (WinDbg attached to the crashing process) shows this is the
well-known **Windows-only Node.js/libuv shutdown race** — `uv_async_send()` is called
on a `uv_async_t` whose `UV_HANDLE_CLOSING` flag is already set, tripping the libuv
assertion:
```
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c
```
which calls `abort()` → `__fastfail(FAST_FAIL_FATAL_APP_EXIT)`. The bundled Node
(v24.16.0) ships libuv with this assertion **enabled**, so the otherwise-benign
teardown race becomes a hard crash.
Upstream tracking issue: https://github.com/nodejs/node/issues/56645
(Node 23/24/25 lines on Windows; Linux/macOS unaffected.)
### Affected version
- **GitHub Copilot CLI:** 1.0.74-0 (crash first captured on 1.0.73.0; reproduces on 1.0.74-0) - **Bundled Node.js:** v24.16.0 (win-x64, single-executable build), ICU 78 - **OS:** Windows x64 (build 10.0.29635) - **Onset:** started ~1 month ago (consistent with a CLI update that bumped the bundled Node)
### Steps to reproduce the behavior
1. Launch `copilot` on Windows x64.
2. Use it normally (any session involving network/async activity).
3. Exit the CLI.
4. The process aborts on exit with a fail-fast crash / WER entry (exit is not clean).
Reproduces every time on the affected machine.
### Expected behavior
The CLI should exit cleanly (exit code 0, no fail-fast/WER crash) after the session ends.
### Additional context
Two threads show both sides of the shutdown race:
**Crashing thread — "V8Worker" (a V8 background platform worker):**
```
copilot!uv_async_send+0x3a
→ _wassert("!(handle->flags & UV_HANDLE_CLOSING)", "src\win\async.c", 94)
→ abort()
→ __fastfail(FAST_FAIL_FATAL_APP_EXIT) ; int 29h, rcx=7
```
Disassembly of `uv_async_send` confirms the fatal branch is the top-of-function
guard (`test byte ptr [handle+0x58],1` / `UV_HANDLE_CLOSING`), not a failed
`PostQueuedCompletionStatus`.
**Main thread — in Node's process-exit teardown, joining workers:**
```
node::DefaultProcessExitHandler
→ (V8 platform shutdown)
→ uv_thread_join
→ WaitForSingleObjectEx ; waiting for the V8 worker to exit
```
So during `DefaultProcessExitHandler`, the main thread closes the per-isolate
"flush-tasks" `uv_async_t` (sets `UV_HANDLE_CLOSING`) and joins the V8 workers; a
V8 worker drains one last task and calls `uv_async_send()` on that now-closing
handle → assertion → abort.
`!analyze -v` summary:
- `ExceptionCode: c0000409` (STATUS_STACK_BUFFER_OVERRUN), `Subcode: 0x7 FAST_FAIL_FATAL_APP_EXIT`
- `FAILURE_BUCKET_ID: FAIL_FAST_FATAL_APP_EXIT_c0000409_copilot.exe!Unknown`
- Faulting thread is the V8 worker; instruction is `int 29h` (`__fastfail`).
## Suggested fixes (for maintainers)
- Avoid `process.exit()` during teardown when V8/libuv async handles are still
live; prefer setting `process.exitCode` and letting the loop drain, or add a
small deferral before forced exit (common community workaround for #56645).
- Ship a Node build with the libuv fix for nodejs/node#56645 once available, or a
Node line where the shutdown ordering doesn't hit this path on Windows.
- Impact is at-exit only (no data loss), but it produces a non-zero exit and a WER
crash entry on every exit, which is noisy and can break scripts that check the
CLI's exit code.
貢獻指南
研究方向
首先追蹤報告中描述的 Node DefaultProcessExitHandler 和 V8 平台關閉流程,重點關注 process.exit 路徑以及每個 isolate 的 flush-tasks uv_async_t。在 Windows x64 上重現退出崩潰,並將隨附的 Node v24.16.0 行為與上游 nodejs/node#56645 追蹤 issue 進行比較。當 copilot 以 code 0 正常退出,且沒有發生 fail-fast 或 WER 崩潰時,即表示完成。
由索引模型根據 Issue 內容生成。
評估
- 技術堆疊
- node.js
- 領域
- cli
- Issue 類型
- 缺陷
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 活躍度
- 冷清
- 描述清晰度
- 基本清楚
- 新手友好度
- 48/100