Voice server permanent deadlock when pid file is deleted while server process survives (bind-loser exits before writing pid)
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Shell
- Sterne
- 11.2k
- Forks
- 1.9k
- Ø Merge
- 14 Std. 16 Min.
- Gemergte PRs (30 T.)
- 6
Beschreibung
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:
- 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).
- 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.
app.jsclient logic reads the pid file to decide whether to connect or spawn:
A missing pid file skips the connect branch entirely, even thoughlet 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) directlyt.addressis already known deterministically and the pipe is live and accepting connections.- The client spawns a new server. The new process computes the same pipe name, tries to bind, and loses to the still-running original.
- 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 - 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)
- In the client (
app.js), when the pid file is missing/stale, attemptconnect(t.address)directly before spawning — the address is already computed deterministically, so this costs nothing and immediately closes the race. - In the server (
voice-server.js), onbind-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>.pidwhile 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.
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginnen Sie in app.js beim Pfad der fehlenden oder veralteten pid-Datei und vergleichen Sie ihn dann mit der bind-loser-Behandlung in voice-server.js. Reproduzieren Sie den Fehler, indem Sie die pid-Datei löschen, während der Server und die Named Pipe weiterhin aktiv sind. Als erledigt gilt dies, wenn ein nachfolgender voice-Aufruf die Verbindung wiederherstellt oder gültige Liveness-Informationen wiederherstellt, statt nach wiederholten Spawn-Versuchen ein Timeout zu erreichen.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- javascript
- Bereich
- backend, cli, operating-systems
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Klar beschrieben
- Anfängerfreundlichkeit
- 64/100