github / github/copilot-cli

Voice server permanent deadlock when pid file is deleted while server process survives (bind-loser exits before writing pid)

Đang mở
#4,740 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

area:platform-windows
Ngôn ngữ chính
Shell
Star
11.2k
Fork
1.9k
Merge trung bình
14 giờ 16 phút
Pull request đã merge (30 ngày)
6

Mô tả

Summary

Voice server enters a permanent deadlock ("Failed to connect to or spawn the voice server after 3 attempts: voice server did not write a valid pid file within 8000ms") whenever its pid file is deleted (e.g. by OS temp-directory cleanup) while the server process itself is still alive and holding the named pipe.

Root cause (traced in shipped 1.0.84-1 win32-x64 build)

Voice IPC uses two independent records of server liveness that can desync:

  • A Windows named pipe \\.\pipe\copilot-voice-<hash> (kernel object, survives independently)
  • A pid file at %TEMP%\copilot-voice\<hash>.pid (the client's only source of truth)

Both <hash> values are derived deterministically: sha256(cliVersion + "|" + sha256(USERPROFILE).slice(0,8)).slice(0,8).

Sequence that produces the deadlock:

  1. A server starts, wins the pipe, writes its pid file. It keeps running (idle-shutdown only arms once its connected-client count reaches 0; clients that die without a clean disconnect can keep that count above 0 indefinitely, so a server can persist for days).
  2. The pid file is removed independently of the process (observed cause: Windows temp-directory cleanup). The pipe is untouched — it's a kernel object, not a file.
  3. app.js client logic reads the pid file to decide whether to connect or spawn:
    let p = readPid(t.pidFile);
    if (p !== null && isAlive(p)) { connect(t.address) /* ... */ }
    // else: falls straight through to spawning a new server — never attempts connect(t.address) directly
    
    A missing pid file skips the connect branch entirely, even though t.address is already known deterministically and the pipe is live and accepting connections.
  4. The client spawns a new server. The new process computes the same pipe name, tries to bind, and loses to the still-running original.
  5. In voice-server.js, the losing branch exits before ever writing a pid file:
    S === "lost" && (log("bind-loser..."), await i.dispose(), process.exit(id)) // id = 10
    // ... pid file write (Lo(t.pidFile, process.pid)) is further down, never reached here
    
  6. The client waits the full pid-file timeout ($_n = 8e3, 8000ms) for a file that can never appear, then retries (WJe = [0,100,500], 3 attempts total), then throws the error above.

Every retry repeats step 4-6 identically — this is a permanent deadlock, not a transient failure. It only resolves if something external kills the original server or manually restores the pid file.

Suggested fixes (either alone fixes it)

  1. In the client (app.js), when the pid file is missing/stale, attempt connect(t.address) directly before spawning — the address is already computed deterministically, so this costs nothing and immediately closes the race.
  2. In the server (voice-server.js), on bind-loser, write the winning pid (which is discoverable from the bind failure) to the pid file before exiting, instead of exiting silently with no pid file at all.

Either change means a deleted pid file can never produce an unrecoverable state as long as the real owning process is still alive.

Environment

  • Copilot CLI 1.0.84-1, win32-x64
  • Reproduced by manually deleting %TEMP%\copilot-voice\<hash>.pid while a previously-started voice server process was still running; the next voice-triggering CLI invocation failed with the exact reported error, deterministically, on every retry.

Workaround used

Killing the orphaned server process (identified via its named pipe and boot-time server log) releases the pipe, allowing a fresh spawn to bind and write its pid file normally. A local sessionStart hook now detects this exact signature (dead/missing pid file + live matching pipe + exactly one candidate copilot.exe --prefer-version <version> process) and self-heals by writing the correct pid before the CLI ever attempts to connect.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong app.js tại đường dẫn pid-file bị thiếu hoặc đã cũ, sau đó so sánh với cách xử lý bind-loser trong voice-server.js. Tái hiện lỗi bằng cách xóa pid-file trong khi server và named pipe vẫn đang hoạt động. Được xem là hoàn tất khi một lần gọi voice tiếp theo kết nối lại hoặc khôi phục thông tin liveness hợp lệ, thay vì bị timeout sau nhiều lần thử spawn.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript
Lĩnh vực
backend, cli, operating-systems
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
64/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.