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
- Lenguaje dominante
- Shell
- Estrellas
- 11.2k
- Forks
- 1.9k
- Merge medio
- 14 h 16 min
- PR fusionados (30 d)
- 6
Descripción
## 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.**
Guía de contribución
Línea de trabajo
Start by locating the CLI's process-exit or stream-flush path described in the report, then reproduce the failure with Python subprocess using PIPE, a regular file, and DEVNULL for stdout. Verify that non-interactive Windows handles no longer cause exit 1 or lose stdout and stderr, while PowerShell redirection remains successful.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- powershell, python, shell
- Área
- cli, operating-systems
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 48/100