Describe the bug
When Copilot CLI runs a streamed BYOK session using an OpenAI-compatible provider with wireApi: "responses", the model can emit a complete raw input for the built-in apply_patch tool, but the CLI invokes apply_patch with an empty argument string.
The SDK event stream contains all assistant.tool_call_delta fragments needed to reconstruct a valid patch. The corresponding tool.execution_start event then reports arguments: "", and execution fails with:
apply_patch requires a non-empty string input (the patch content).
This was reproduced with both Copilot CLI 1.0.74-0 and 1.0.78-2. The standalone reproducer below uses only the official Go SDK and the Copilot CLI; it does not depend on an application framework.
Affected version
GitHub Copilot CLI 1.0.78-2
github.com/github/copilot-sdk/go v1.0.8
go version go1.26.5 windows/amd64
Windows 11 Pro 10.0.26200, amd64
Also reproduced on Copilot CLI 1.0.74-0.
Steps to reproduce the behavior
-
Install and authenticate Copilot CLI.
-
Create an empty directory and initialize the reproducer:
go mod init example.com/copilot-apply-patch-repro
go get github.com/github/copilot-sdk/go@v1.0.8
-
Save the following as main.go:
Standalone Go reproducer
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
copilot "github.com/github/copilot-sdk/go"
)
type evidence struct {
deltaCount int
delta strings.Builder
arguments any
callID string
success bool
toolError string
}
func main() {
baseURL := os.Getenv("COPILOT_REPRO_BASE_URL")
apiKey := os.Getenv("COPILOT_REPRO_API_KEY")
model := os.Getenv("COPILOT_REPRO_MODEL")
if baseURL == "" || apiKey == "" || model == "" {
log.Fatal("COPILOT_REPRO_BASE_URL, COPILOT_REPRO_API_KEY, and COPILOT_REPRO_MODEL are required")
}
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
target := filepath.Join(wd, "target.txt")
if err := os.WriteFile(target, []byte("ORIGINAL_STREAMING_PROBE\n"), 0o600); err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
client := copilot.NewClient(nil)
if err := client.Start(ctx); err != nil {
log.Fatal(err)
}
defer client.Stop()
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: model,
ReasoningEffort: "low",
Streaming: copilot.Bool(true),
WorkingDirectory: wd,
AvailableTools: []string{"apply_patch"},
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
Provider: &copilot.ProviderConfig{
Type: "openai", WireAPI: "responses", BaseURL: baseURL, APIKey: apiKey,
},
SkipCustomInstructions: copilot.Bool(true),
EnableConfigDiscovery: copilot.Bool(false),
EnableSkills: copilot.Bool(false),
})
if err != nil {
log.Fatal(err)
}
defer session.Disconnect()
var got evidence
session.On(func(event copilot.SessionEvent) {
switch data := event.Data.(type) {
case *copilot.AssistantToolCallDeltaData:
if data.ToolName != nil && *data.ToolName == "apply_patch" {
got.deltaCount++
got.delta.WriteString(data.InputDelta)
}
case *copilot.ToolExecutionStartData:
if data.ToolName == "apply_patch" {
got.callID = data.ToolCallID
got.arguments = data.Arguments
}
case *copilot.ToolExecutionCompleteData:
if data.ToolCallID == got.callID {
got.success = data.Success
if data.Error != nil {
got.toolError = data.Error.Message
}
}
}
})
prompt := fmt.Sprintf(`Call the built-in apply_patch tool exactly once. Do not call any other tool.
Replace the complete line ORIGINAL_STREAMING_PROBE with PATCHED_STREAMING_PROBE in this existing file:
%s
Do not retry if apply_patch fails.`, filepath.ToSlash(target))
if _, err := session.SendPromptAndWait(ctx, prompt); err != nil {
log.Print(err)
}
args, _ := json.Marshal(got.arguments)
content, _ := os.ReadFile(target)
fmt.Printf("delta_count=%d\ndelta=%q\narguments=%s\nsuccess=%t\nerror=%q\ntarget=%q\n",
got.deltaCount, got.delta.String(), args, got.success, got.toolError, content)
}
-
Configure any OpenAI-compatible Responses provider and run:
$env:COPILOT_REPRO_BASE_URL = "https://your-openai-compatible-endpoint"
$env:COPILOT_REPRO_API_KEY = "..."
$env:COPILOT_REPRO_MODEL = "your-model"
go run .
-
Observe that the reconstructed delta is a valid patch while execution arguments are empty.
Actual sanitized output from 1.0.78-2:
delta_count=55
delta_length=174
delta_sha256=93917537028d251c59aedc8c2791697bf689214973906e641dfec977e3f1a15e
delta_input_json="*** Begin Patch\n*** Update File: D:\\project\\r42\\.r42\\diagnostics\\copilot-sdk-apply-patch-repro\\target.txt\n@@\n-ORIGINAL_STREAMING_PROBE\n+PATCHED_STREAMING_PROBE\n*** End Patch\n"
execution_arguments_json=""
tool_success=false
tool_error="apply_patch requires a non-empty string input (the patch content)."
target_content="ORIGINAL_STREAMING_PROBE\n"
The relevant event transition is:
assistant.tool_call_delta: 55 fragments -> complete 174-byte patch
tool.execution_start: {"toolName":"apply_patch","arguments":""}
tool.execution_complete: success=false, non-empty-string error
Expected behavior
The accumulated raw custom-tool input should be passed to the built-in apply_patch invocation. tool.execution_start.arguments should contain the patch, execution should succeed, and target.txt should contain PATCHED_STREAMING_PROBE.
Additional context
Control result
On the same machine and the same CLI version, a logged-in GitHub-hosted gpt-5.4 session succeeds with the same prompt, streaming enabled, and only apply_patch available:
copilot --model gpt-5.4 --stream on --available-tools=apply_patch --allow-all `
--no-custom-instructions --no-experimental -p "<same prompt>"
The command exits 0, reports one line added and one removed, and the file contains PATCHED_STREAMING_PROBE.
Analysis
The provider/model output is not empty: the CLI has already decoded and emitted the complete raw custom-tool input through assistant.tool_call_delta. The file path and write permission are also valid, as shown by the GitHub-hosted control run. The value becomes empty between the emitted deltas and the built-in tool execution event.
This suggests a fidelity issue in the BYOK OpenAI Responses streaming path when converting a streamed custom tool call into the built-in apply_patch invocation. I cannot determine from the public artifacts whether the fix belongs in the Responses adapter or the runtime tool dispatcher, but the event boundary above should provide a narrow regression test.
I am filing a corresponding SDK issue because this is directly observable through the public Go SDK event contract. I will cross-link it here once created.
Describe the bug
When Copilot CLI runs a streamed BYOK session using an OpenAI-compatible provider with
wireApi: "responses", the model can emit a complete raw input for the built-inapply_patchtool, but the CLI invokesapply_patchwith an empty argument string.The SDK event stream contains all
assistant.tool_call_deltafragments needed to reconstruct a valid patch. The correspondingtool.execution_startevent then reportsarguments: "", and execution fails with:This was reproduced with both Copilot CLI
1.0.74-0and1.0.78-2. The standalone reproducer below uses only the official Go SDK and the Copilot CLI; it does not depend on an application framework.Affected version
Also reproduced on Copilot CLI
1.0.74-0.Steps to reproduce the behavior
Install and authenticate Copilot CLI.
Create an empty directory and initialize the reproducer:
Save the following as
main.go:Standalone Go reproducer
Configure any OpenAI-compatible Responses provider and run:
Observe that the reconstructed delta is a valid patch while execution arguments are empty.
Actual sanitized output from
1.0.78-2:The relevant event transition is:
Expected behavior
The accumulated raw custom-tool input should be passed to the built-in
apply_patchinvocation.tool.execution_start.argumentsshould contain the patch, execution should succeed, andtarget.txtshould containPATCHED_STREAMING_PROBE.Additional context
Control result
On the same machine and the same CLI version, a logged-in GitHub-hosted
gpt-5.4session succeeds with the same prompt, streaming enabled, and onlyapply_patchavailable:The command exits
0, reports one line added and one removed, and the file containsPATCHED_STREAMING_PROBE.Analysis
The provider/model output is not empty: the CLI has already decoded and emitted the complete raw custom-tool input through
assistant.tool_call_delta. The file path and write permission are also valid, as shown by the GitHub-hosted control run. The value becomes empty between the emitted deltas and the built-in tool execution event.This suggests a fidelity issue in the BYOK OpenAI Responses streaming path when converting a streamed custom tool call into the built-in
apply_patchinvocation. I cannot determine from the public artifacts whether the fix belongs in the Responses adapter or the runtime tool dispatcher, but the event boundary above should provide a narrow regression test.I am filing a corresponding SDK issue because this is directly observable through the public Go SDK event contract. I will cross-link it here once created.