github / github/copilot-cli

Windows: copilot.exe exits 1 with empty streams when stdout is a pipe / regular file (FlushFileBuffers ERROR_INVALID_FUNCTION) — breaks all non-PowerShell automation on v1.0.44-0

Offen
#3,188 1 Kommentar 3 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area:non-interactive area:platform-windows
Vorherrschende Sprache
Shell
Sterne
11.2k
Forks
1.9k
Ø Merge
14 Std. 16 Min.
Gemergte PRs (30 T.)
6

Beschreibung

## Summary

GitHub Copilot CLI (`copilot.exe`, v1.0.44-0 on Windows) exits with code **1** and produces **empty stdout / empty stderr** when invoked from a child process whose stdout handle is anything other than a PowerShell native `>` redirect (or an interactive console).

When stdout is set to `DEVNULL` and stderr is captured separately, the underlying error becomes visible:

```
Error: Failed to sync '': Incorrect function.
```

This is `ERROR_INVALID_FUNCTION` (Win32 error 1) returned by `FlushFileBuffers` on a handle that does not support buffer flushing — typically anonymous pipes, `NUL` device, or files opened without the right flags. The CLI appears to call a sync/flush operation on stdout at exit and treats its failure as a fatal error, dropping the actual response from stdout and never writing the error to stderr unless stderr is captured independently.

This breaks every CI/automation/daemon scenario on Windows that uses `subprocess.Popen` (Python), `Process.Start` (.NET), `child_process.spawn` (Node), or any other launcher that creates pipe / regular-file stdout handles.

## Environment

- **Copilot CLI**: 1.0.44-0 (`copilot --version`)
- **OS**: Windows 11
- **Install**: WinGet (`copilot.exe` is a native binary, not an npm shim)
- **Auth**: `copilot auth` shows a valid GitHub login

## Reproduction

### Working (PowerShell direct, file redirect)

```powershell
copilot --prompt "respond with one word ok" --model claude-sonnet-4.5 --allow-all --no-ask-user > out.txt 2> err.txt
$LASTEXITCODE # 0 (success)
```

### Failing (Python subprocess, PIPE)

```python
import subprocess
proc = subprocess.Popen(
["copilot", "--prompt", "respond with one word ok",
"--model", "claude-sonnet-4.5", "--allow-all", "--no-ask-user"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
out, err = proc.communicate(timeout=120)
print(proc.returncode) # 1
print(repr(out)) # ''
print(repr(err)) # ''
```

### Failing (Python subprocess, regular file)

```python
import subprocess
out_f = open("out.txt", "wb")
err_f = open("err.txt", "wb")
proc = subprocess.Popen(
["copilot", "--prompt", "respond with one word ok",
"--model", "claude-sonnet-4.5", "--allow-all", "--no-ask-user"],
stdin=subprocess.DEVNULL, stdout=out_f, stderr=err_f,
)
proc.wait(timeout=120)
print(proc.returncode) # 1
print(open("out.txt","rb").read()) # b''
print(open("err.txt","rb").read()) # b'' (no message at all)
```

### Failing (cmd.exe with redirect)

```python
proc = subprocess.Popen(
["cmd.exe", "/c",
'copilot --prompt "respond with one word ok" --model claude-sonnet-4.5 --allow-all --no-ask-user > out.txt 2> err.txt'],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
)
proc.communicate(timeout=120)
# Exit 1, no files created.
```

### Surfaces the actual error (Python subprocess, DEVNULL stdout + PIPE stderr)

```python
proc = subprocess.Popen(
["copilot", "--prompt", "respond with one word ok",
"--model", "claude-sonnet-4.5", "--allow-all", "--no-ask-user"],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
)
out, err = proc.communicate(timeout=120)
print(proc.returncode) # 1
print(err)
# Error: Failed to sync '': Incorrect function.
```

### Working (Python subprocess, wrapped in `pwsh.exe -c "... > out 2> err"`)

```python
proc = subprocess.Popen(
["pwsh.exe", "-NoProfile", "-NonInteractive", "-Command",
'copilot --prompt "respond with one word ok" --model "claude-sonnet-4.5" --allow-all --no-ask-user > out.txt 2> err.txt ; exit $LASTEXITCODE'],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
)
proc.communicate(timeout=180)
# Exit 0, out.txt has response, err.txt has the normal token-summary footer.
```

This confirms the workaround and isolates the bug to **how the CLI's runtime treats the stdout handle when it isn't the kind PowerShell's native `>` operator creates**.

## Expected behavior

- The CLI should not call `FlushFileBuffers` (or equivalent) on stdout when the handle does not support it. `GetFileType()` on the handle would let it skip the flush for `FILE_TYPE_PIPE`, `FILE_TYPE_CHAR`, etc.
- Failing that, a flush failure at exit should not be promoted to a fatal exit-1 — the actual response data has already been written.
- At minimum, the error message should be flushed to stderr (with a `flush()` before `exit()`) so consumers can diagnose the failure. Today the message is lost in PIPE/PIPE mode because the panic path apparently aborts before stderr is flushed.

## Actual behavior

- Exit code 1 with no output on either stream
- Buffered token-summary footer that normally goes to stderr is also lost
- Every CI/automation tool on Windows fails silently

## Impact

This blocks all programmatic use of Copilot CLI from Windows automation:

- Daemons / background workers (e.g., PR-review bots that invoke `copilot`)
- CI pipelines that aren't wrapped in PowerShell
- IDE integrations that use `child_process.spawn` / equivalent
- Any non-interactive scenario using Python, Node, .NET, Go, Rust, etc.

The current workaround (wrap every invocation in `pwsh.exe -NoProfile -NonInteractive -Command "... > out 2> err"`) adds an extra process per call and is fragile (string-encoded args, encoding issues with the temp files, etc.).

## Suggested fix

In the CLI's process-exit / stream-flush path, gate the explicit stdout sync on `GetFileType(stdout) == FILE_TYPE_DISK`, or wrap it in a try/catch that does not promote the failure to exit code 1 (since the data has already been written).

Independently, make sure stderr is flushed before exit so that any startup / runtime error is observable from automation.

## Notes

- I previously filed #3186 which I will close — that one was based on a broken test harness (PowerShell `Start-Process -ArgumentList` mangles argv around spaces) and incorrectly attributed the failure to `-p`/`--prompt` tokenization. **This issue is the corrected analysis with proof.**

Beitragsleitfaden

Beitragsleitfaden öffnen

Rechercherichtung

Beginne damit, den im Bericht beschriebenen Pfad für den Prozess-Exit oder das Leeren des Streams der CLI zu finden, und reproduziere den Fehler anschließend mit Python subprocess unter Verwendung von PIPE, einer regulären Datei und DEVNULL für stdout. Verifiziere, dass nicht interaktive Windows-Handles nicht mehr Exit 1 verursachen und stdout und stderr nicht verloren gehen, während die Umleitung in PowerShell weiterhin erfolgreich bleibt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
powershell, python, shell
Bereich
cli, operating-systems
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.