Skip to content
Merged
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
37 changes: 37 additions & 0 deletions cmd/root/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package root

import (
"context"
"fmt"
"log/slog"
"os"
Expand Down Expand Up @@ -50,16 +51,52 @@ func newAPICmd() *cobra.Command {
return cmd
}

// monitorStdin monitors stdin for EOF, which indicates the parent process has died.
// When spawned with piped stdio, stdin closes when the parent process dies.
func monitorStdin(ctx context.Context, cancel context.CancelFunc, stdin *os.File) {
// Close stdin when context is cancelled to unblock the read
go func() {
<-ctx.Done()
stdin.Close()
}()

buf := make([]byte, 1)
for {
n, err := stdin.Read(buf)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: could this block the goroutine in other scenarios like normal shutdown if we don't check if the context has already been cancelled? probably not a big issue though since the proc should die anyway in most other scenarios

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I pushed a second commit

if err != nil || n == 0 {
// Only log and cancel if context isn't already done (parent died)
if ctx.Err() == nil {
slog.Info("stdin closed, parent process likely died, shutting down")
cancel()
}
return
}
}
}

func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error {
telemetry.TrackCommand("api", args)

ctx := cmd.Context()

// Save stdin before clearing it, so we can monitor for parent death
stdin := os.Stdin

out := cli.NewPrinter(cmd.OutOrStdout())
agentsPath := args[0]

// Make sure no question is ever asked to the user in api mode.
os.Stdin = nil

// Monitor stdin for EOF to detect parent process death.
// When spawned with piped stdio, stdin closes when the parent process dies.
if stdin != nil {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
go monitorStdin(ctx, cancel, stdin)
}

// Start fake proxy if --fake is specified
cleanup, err := setupFakeProxy(f.fakeResponses, &f.runConfig)
if err != nil {
Expand Down