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 '<stdout>': 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)
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)
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)
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)
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)
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 '<stdout>': Incorrect function.
Working (Python subprocess, wrapped in pwsh.exe -c "... > out 2> err")
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
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
DEVNULLand stderr is captured separately, the underlying error becomes visible:This is
ERROR_INVALID_FUNCTION(Win32 error 1) returned byFlushFileBufferson a handle that does not support buffer flushing — typically anonymous pipes,NULdevice, 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 --version)copilot.exeis a native binary, not an npm shim)copilot authshows a valid GitHub loginReproduction
Working (PowerShell direct, file redirect)
Failing (Python subprocess, PIPE)
Failing (Python subprocess, regular file)
Failing (cmd.exe with redirect)
Surfaces the actual error (Python subprocess, DEVNULL stdout + PIPE stderr)
Working (Python subprocess, wrapped in
pwsh.exe -c "... > out 2> err")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
FlushFileBuffers(or equivalent) on stdout when the handle does not support it.GetFileType()on the handle would let it skip the flush forFILE_TYPE_PIPE,FILE_TYPE_CHAR, etc.flush()beforeexit()) 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
Impact
This blocks all programmatic use of Copilot CLI from Windows automation:
copilot)child_process.spawn/ equivalentThe 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
Start-Process -ArgumentListmangles argv around spaces) and incorrectly attributed the failure to-p/--prompttokenization. This issue is the corrected analysis with proof.