From 98b31555f5621ca67792301caa08192a9255db29 Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Fri, 19 Sep 2025 13:33:17 -0400 Subject: [PATCH 1/3] Call patch invoke on control + c --- cmd/invoke.go | 65 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/cmd/invoke.go b/cmd/invoke.go index 0fa4438..9d29c80 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -59,19 +59,13 @@ func runInvoke(cmd *cobra.Command, args []string) error { } params.Payload = kernel.Opt(payloadStr) } - // we don't really care to cancel the context, we just want to handle signals - ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) - cmd.SetContext(ctx) + // Create a separate signal context for cancellation so we don't cancel + // the invocation creation request. + sigCtx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) pterm.Info.Printf("Invoking \"%s\" (action: %s, version: %s)…\n", appName, actionName, version) - // Create the invocation - resp, err := client.Invocations.New(cmd.Context(), params, option.WithMaxRetries(0)) - if err != nil { - return handleSdkError(err) - } - // coordinate the cleanup with the polling loop to ensure this is given enough time to run - // before this function returns + // Coordinate cleanup across all early-returns cleanupDone := make(chan struct{}) cleanupStarted := atomic.Bool{} defer func() { @@ -80,6 +74,41 @@ func runInvoke(cmd *cobra.Command, args []string) error { } }() + // Track invocation ID once created so we can mark it failed on cancel + var invocationID string + // Ensure we only run cancel cleanup once + once := sync.Once{} + // When cancelled, explicitly mark the invocation as failed via the update endpoint + onCancel(sigCtx, func() { + once.Do(func() { + cleanupStarted.Store(true) + defer close(cleanupDone) + pterm.Warning.Println("Invocation cancelled...cleaning up...") + if invocationID != "" { + if _, err := client.Invocations.Update( + context.Background(), + invocationID, + kernel.InvocationUpdateParams{ + Status: kernel.InvocationUpdateParamsStatusFailed, + Output: kernel.Opt(`{"error":"Invocation cancelled by user"}`), + }, + option.WithRequestTimeout(30*time.Second), + ); err != nil { + pterm.Error.Printf("Failed to mark invocation as failed: %v\n", err) + } + } else { + pterm.Warning.Println("Cancellation received before invocation was created.") + } + }) + }) + + // Create the invocation + resp, err := client.Invocations.New(cmd.Context(), params, option.WithMaxRetries(0)) + if err != nil { + return handleSdkError(err) + } + invocationID = resp.ID + if resp.Status != kernel.InvocationNewResponseStatusQueued { succeeded := resp.Status == kernel.InvocationNewResponseStatusSucceeded printResult(succeeded, resp.Output) @@ -92,22 +121,10 @@ func runInvoke(cmd *cobra.Command, args []string) error { return nil } - // this is a little indirect but we try to fail out of the invocation by deleting the - // underlying browser sessions - once := sync.Once{} - onCancel(cmd.Context(), func() { - once.Do(func() { - cleanupStarted.Store(true) - defer close(cleanupDone) - pterm.Warning.Println("Invocation cancelled...cleaning up...") - if err := client.Invocations.DeleteBrowsers(context.Background(), resp.ID, option.WithRequestTimeout(30*time.Second)); err != nil { - pterm.Error.Printf("Failed to cancel invocation: %v\n", err) - } - }) - }) + // cancellation handler already registered above // Start following events - stream := client.Invocations.FollowStreaming(cmd.Context(), resp.ID, option.WithMaxRetries(0)) + stream := client.Invocations.FollowStreaming(sigCtx, resp.ID, option.WithMaxRetries(0)) for stream.Next() { ev := stream.Current() From 327e33757fe3eae94a43478369904416cdd31a96 Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Fri, 19 Sep 2025 16:39:56 -0400 Subject: [PATCH 2/3] Remove unnecessary changes --- cmd/invoke.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/invoke.go b/cmd/invoke.go index 9d29c80..1e08fe2 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -59,9 +59,9 @@ func runInvoke(cmd *cobra.Command, args []string) error { } params.Payload = kernel.Opt(payloadStr) } - // Create a separate signal context for cancellation so we don't cancel - // the invocation creation request. - sigCtx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + // we don't really care to cancel the context, we just want to handle signals + ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM) + cmd.SetContext(ctx) pterm.Info.Printf("Invoking \"%s\" (action: %s, version: %s)…\n", appName, actionName, version) @@ -79,7 +79,7 @@ func runInvoke(cmd *cobra.Command, args []string) error { // Ensure we only run cancel cleanup once once := sync.Once{} // When cancelled, explicitly mark the invocation as failed via the update endpoint - onCancel(sigCtx, func() { + onCancel(cmd.Context(), func() { once.Do(func() { cleanupStarted.Store(true) defer close(cleanupDone) @@ -124,7 +124,7 @@ func runInvoke(cmd *cobra.Command, args []string) error { // cancellation handler already registered above // Start following events - stream := client.Invocations.FollowStreaming(sigCtx, resp.ID, option.WithMaxRetries(0)) + stream := client.Invocations.FollowStreaming(cmd.Context(), resp.ID, option.WithMaxRetries(0)) for stream.Next() { ev := stream.Current() From 1f862862978b1e0ae17f75fed26907b22fc4ea9e Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Fri, 19 Sep 2025 16:51:13 -0400 Subject: [PATCH 3/3] invoke: mark invocation failed on cancel --- cmd/invoke.go | 64 ++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/cmd/invoke.go b/cmd/invoke.go index 1e08fe2..579f8d7 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -65,7 +65,13 @@ func runInvoke(cmd *cobra.Command, args []string) error { pterm.Info.Printf("Invoking \"%s\" (action: %s, version: %s)…\n", appName, actionName, version) - // Coordinate cleanup across all early-returns + // Create the invocation + resp, err := client.Invocations.New(cmd.Context(), params, option.WithMaxRetries(0)) + if err != nil { + return handleSdkError(err) + } + // coordinate the cleanup with the polling loop to ensure this is given enough time to run + // before this function returns cleanupDone := make(chan struct{}) cleanupStarted := atomic.Bool{} defer func() { @@ -74,41 +80,6 @@ func runInvoke(cmd *cobra.Command, args []string) error { } }() - // Track invocation ID once created so we can mark it failed on cancel - var invocationID string - // Ensure we only run cancel cleanup once - once := sync.Once{} - // When cancelled, explicitly mark the invocation as failed via the update endpoint - onCancel(cmd.Context(), func() { - once.Do(func() { - cleanupStarted.Store(true) - defer close(cleanupDone) - pterm.Warning.Println("Invocation cancelled...cleaning up...") - if invocationID != "" { - if _, err := client.Invocations.Update( - context.Background(), - invocationID, - kernel.InvocationUpdateParams{ - Status: kernel.InvocationUpdateParamsStatusFailed, - Output: kernel.Opt(`{"error":"Invocation cancelled by user"}`), - }, - option.WithRequestTimeout(30*time.Second), - ); err != nil { - pterm.Error.Printf("Failed to mark invocation as failed: %v\n", err) - } - } else { - pterm.Warning.Println("Cancellation received before invocation was created.") - } - }) - }) - - // Create the invocation - resp, err := client.Invocations.New(cmd.Context(), params, option.WithMaxRetries(0)) - if err != nil { - return handleSdkError(err) - } - invocationID = resp.ID - if resp.Status != kernel.InvocationNewResponseStatusQueued { succeeded := resp.Status == kernel.InvocationNewResponseStatusSucceeded printResult(succeeded, resp.Output) @@ -121,7 +92,26 @@ func runInvoke(cmd *cobra.Command, args []string) error { return nil } - // cancellation handler already registered above + // On cancel, mark the invocation as failed via the update endpoint + once := sync.Once{} + onCancel(cmd.Context(), func() { + once.Do(func() { + cleanupStarted.Store(true) + defer close(cleanupDone) + pterm.Warning.Println("Invocation cancelled...cleaning up...") + if _, err := client.Invocations.Update( + context.Background(), + resp.ID, + kernel.InvocationUpdateParams{ + Status: kernel.InvocationUpdateParamsStatusFailed, + Output: kernel.Opt(`{"error":"Invocation cancelled by user"}`), + }, + option.WithRequestTimeout(30*time.Second), + ); err != nil { + pterm.Error.Printf("Failed to mark invocation as failed: %v\n", err) + } + }) + }) // Start following events stream := client.Invocations.FollowStreaming(cmd.Context(), resp.ID, option.WithMaxRetries(0))