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

オープン
#3,188 コメント 1 件 リアクション 3 件 担当者 0 名 GitHub で見る
area:non-interactive area:platform-windows
主要言語
Shell
スター
11.2k
フォーク
1.9k
平均マージ
14時間 16分
マージ済み PR(30日)
6

説明

## 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.**

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
powershell, python, shell
領域
cli, operating-systems
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。