Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module bauer
go 1.24.0

require (
github.com/github/copilot-sdk/go v0.1.15
github.com/github/copilot-sdk/go v0.2.1-preview.1
github.com/google/go-cmp v0.7.0
golang.org/x/oauth2 v0.33.0
google.golang.org/api v0.257.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/github/copilot-sdk/go v0.1.15 h1:JmF0DbF1n007FyTfjagfCm4epAW4NIOlCFYP/VXtgXM=
github.com/github/copilot-sdk/go v0.1.15/go.mod h1:0SYT+64k347IDT0Trn4JHVFlUhPtGSE6ab479tU/+tY=
github.com/github/copilot-sdk/go v0.2.1-preview.1 h1:+uRwtARthFmbNVeafEcSkFKeimwrAUlD84KH+kakEmo=
github.com/github/copilot-sdk/go v0.2.1-preview.1/go.mod h1:uGWkjVYcp2DV9DgtqYihh5tEoJjNqxIFaUNnrwY4FxM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down
47 changes: 23 additions & 24 deletions internal/copilotcli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ func NewClient(cwd string) (*Client, error) {
// Start starts the Copilot CLI server
func (c *Client) Start() error {
slog.Info("Starting Copilot client...")
if err := c.client.Start(); err != nil {
if err := c.client.Start(context.Background()); err != nil {
return fmt.Errorf("failed to start Copilot client: %w", err)
}

// Verify connectivity with ping
_, err := c.client.Ping("health-check")
_, err := c.client.Ping(context.Background(), "health-check")
Comment on lines +85 to +90
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start() and Ping() are called with context.Background(), so if the CLI blocks/hangs during startup or health-check this can hang indefinitely and cannot be cancelled by the caller. Consider threading a caller-provided context into Start(ctx) (preferred) or at least using a context.WithTimeout for both calls (and returning a timeout error) to avoid stuck orchestrations.

Copilot uses AI. Check for mistakes.
if err != nil {
c.client.Stop()
_ = c.client.Stop()
return fmt.Errorf("Copilot client ping failed: %w", err)
Comment on lines 91 to 93
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On ping failure, the shutdown error from c.client.Stop() is discarded. Since Stop() now returns a single error, it would be better to capture and log (or wrap) it so we don’t silently leak a running CLI process when ping fails.

Copilot uses AI. Check for mistakes.
}

Expand All @@ -100,12 +100,9 @@ func (c *Client) Start() error {
// Stop gracefully stops the Copilot CLI server
func (c *Client) Stop() error {
slog.Info("Stopping Copilot client...")
errs := c.client.Stop()
if len(errs) > 0 {
for _, err := range errs {
slog.Error("Error during Copilot client shutdown", slog.String("error", err.Error()))
}
return fmt.Errorf("encountered %d errors during shutdown", len(errs))
if err := c.client.Stop(); err != nil {
slog.Error("Error during Copilot client shutdown", slog.String("error", err.Error()))
return fmt.Errorf("error during Copilot client shutdown: %w", err)
}
slog.Info("Copilot client stopped successfully")
return nil
Expand All @@ -119,16 +116,17 @@ func (c *Client) ExecuteChunk(ctx context.Context, chunkPath string, chunkNumber
)

// Create a session with streaming enabled
session, err := c.client.CreateSession(&copilot.SessionConfig{
Model: model,
Streaming: true,
session, err := c.client.CreateSession(ctx, &copilot.SessionConfig{
Model: model,
Streaming: true,
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
Comment on lines +119 to 123
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnPermissionRequest: copilot.PermissionHandler.ApproveAll will automatically approve any permission prompts from the CLI. If permission requests can include filesystem/network/tool execution, this becomes a broad escalation surface. Consider making this configurable (e.g., default deny/interactive prompt, with an explicit --auto-approve flag for CI), and/or scoping approvals to the minimum set of permissions required for chunk execution.

Copilot uses AI. Check for mistakes.
if err != nil {
return "", fmt.Errorf("failed to create session for chunk %d: %w", chunkNumber, err)
}
defer func() {
if err := session.Destroy(); err != nil {
slog.Error("Failed to destroy session",
if err := session.Disconnect(); err != nil {
slog.Error("Failed to disconnect session",
slog.Int("chunk", chunkNumber),
slog.String("error", err.Error()),
)
Expand Down Expand Up @@ -220,13 +218,13 @@ func (c *Client) ExecuteChunk(ctx context.Context, chunkPath string, chunkNumber
slog.String("file", absChunkPath),
)

_, err = session.Send(copilot.MessageOptions{
_, err = session.Send(ctx, copilot.MessageOptions{
Prompt: fmt.Sprintf("Implement the changes described in @%s. Follow all instructions carefully and apply changes in order.", filepath.Base(chunkPath)),
Attachments: []copilot.Attachment{
{
Type: copilot.File,
Path: absChunkPath,
DisplayName: fmt.Sprintf("chunk-%d.md", chunkNumber),
Type: copilot.AttachmentTypeFile,
Path: copilot.String(absChunkPath),
DisplayName: copilot.String(fmt.Sprintf("chunk-%d.md", chunkNumber)),
},
},
})
Expand Down Expand Up @@ -263,16 +261,17 @@ func (c *Client) GenerateSummary(ctx context.Context, outputs []ChunkOutput, mod
slog.Info("Creating summary session", slog.String("model", model))

// Create a session with streaming enabled
session, err := c.client.CreateSession(&copilot.SessionConfig{
Model: model,
Streaming: true,
session, err := c.client.CreateSession(ctx, &copilot.SessionConfig{
Model: model,
Streaming: true,
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
Comment on lines +264 to 268
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnPermissionRequest: copilot.PermissionHandler.ApproveAll auto-approves all permission prompts for the summary session as well. Even if chunk execution is trusted, summary generation likely doesn’t require extra permissions; consider using a more restrictive handler here or reusing a configurable policy to avoid unnecessary blanket approvals.

Copilot uses AI. Check for mistakes.
if err != nil {
return fmt.Errorf("failed to create summary session: %w", err)
}
defer func() {
if err := session.Destroy(); err != nil {
slog.Error("Failed to destroy summary session", slog.String("error", err.Error()))
if err := session.Disconnect(); err != nil {
slog.Error("Failed to disconnect summary session", slog.String("error", err.Error()))
}
}()

Expand Down Expand Up @@ -324,7 +323,7 @@ func (c *Client) GenerateSummary(ctx context.Context, outputs []ChunkOutput, mod

slog.Info("Sending summary prompt to Copilot")

_, err = session.Send(copilot.MessageOptions{
_, err = session.Send(ctx, copilot.MessageOptions{
Prompt: summaryPrompt,
})
if err != nil {
Expand Down