Skip to content

fix: proper end-to-end Ctrl-C / signal handling for all gh aw commands (WSL)#21795

Merged
pelikhan merged 12 commits intomainfrom
copilot/fix-ctrl-c-handling-issues
Mar 19, 2026
Merged

fix: proper end-to-end Ctrl-C / signal handling for all gh aw commands (WSL)#21795
pelikhan merged 12 commits intomainfrom
copilot/fix-ctrl-c-handling-issues

Conversation

Copy link
Contributor

Copilot AI commented Mar 19, 2026

Ctrl-C failed to terminate gh aw processes reliably — especially on WSL — because there was no top-level signal handler and context cancellation was not propagated through the command stack.

Root causes

  • main() called rootCmd.Execute() with no signal-aware context — whether Ctrl-C did anything useful depended entirely on what ad-hoc handlers happened to be active
  • ExecuteWithRepeat (--repeat) only checked the signal channel at the start of each iteration; a long-running iteration would not be interrupted mid-flight
  • WaitForWorkflowCompletion accepted no context and treated interrupt errors as non-fatal warnings, so the repeat loop silently continued after Ctrl-C
  • Interactive huh forms used form.Run() instead of form.RunWithContext(ctx), so context cancellation couldn't reach them
  • The gh workflow run subprocess used ExecGH (no context), so it couldn't be killed on cancellation
  • PollFunc had no context parameter, so a hanging poll operation (e.g. a stalled gh run view call) could not be aborted mid-call on Ctrl-C

Changes

Top-level signal handling

  • main(): use signal.NotifyContext + rootCmd.ExecuteContext(ctx) — the canonical Go pattern; a single clean cancellation point for the entire process

Context propagation

  • PollOptions / RepeatOptions / EngineSecretConfig / AddInteractiveConfig: add Ctx context.Context field (optional; falls back to context.Background())
  • PollWithSignalHandling: check both ctx.Done() and sigChan in the poll loop; pass ctx to each PollFunc invocation so callbacks can abort mid-call
  • PollOptions.PollFunc: signature changed from func() (PollResult, error) to func(ctx context.Context) (PollResult, error) — callers forward ctx to context-aware operations (e.g. RunGHContext) for true mid-call cancellation
  • ExecuteWithRepeat: check both ctx.Done() and sigChan at the top of the iteration loop; return ctx.Err() / context.Canceled on interruption so the process exits with a non-zero status
  • WaitForWorkflowCompletion: new ctx context.Context parameter; passes it to the poll loop and to the PollFunc callback; propagate ErrInterrupted / ctx.Err() so callers (repeat loop) can stop immediately

Subprocess cancellation

  • gh workflow run subprocess: switch from ExecGHExecGHContext(ctx, ...) so it is killed when context is cancelled

Interactive forms

  • All huh forms in run_interactive.go, interactive.go, add_interactive_*, and engine_secrets.go: replaced form.Run() with form.RunWithContext(ctx)

Sentinel error

  • Define ErrInterrupted in signal_aware_poll.go and use errors.Is(err, ErrInterrupted) instead of fragile string comparison

Tests

  • TestPollWithSignalHandling_ContextCancellation: verifies ErrInterrupted is returned when context is cancelled mid-poll
  • TestPollWithSignalHandling_AlreadyCancelledContext: verifies ErrInterrupted is returned immediately when an already-cancelled context is passed
  • TestWaitForWorkflowCompletion_ContextCancellation: verifies WaitForWorkflowCompletion propagates a cancellation error so callers detect and stop on interruption

[!WARNING]


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

- Add signal.NotifyContext + ExecuteContext in main() for top-level
  SIGINT/SIGTERM handling
- Add Ctx to PollOptions/RepeatOptions/EngineSecretConfig/AddInteractiveConfig
- Define ErrInterrupted sentinel error in signal_aware_poll.go
- Add ctx parameter to WaitForWorkflowCompletion and propagate interrupts
- Use ExecGHContext for gh workflow run subprocess
- Use RunWithContext(ctx) for all huh interactive forms
- Pass context through interactive add-wizard and run command flows

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Ctrl-C handling issues in WSL for gh commands fix: proper end-to-end Ctrl-C / signal handling for all gh aw commands (WSL) Mar 19, 2026
Copilot AI requested a review from dsyme March 19, 2026 13:37
@pelikhan pelikhan marked this pull request as ready for review March 19, 2026 13:58
Copilot AI review requested due to automatic review settings March 19, 2026 13:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improve Ctrl‑C / signal handling across gh aw commands (notably on WSL) by introducing a top-level cancelable context and propagating it through polling, repeating, interactive prompts, and subprocess execution.

Changes:

  • Add top-level signal.NotifyContext and execute Cobra commands with ExecuteContext(ctx).
  • Propagate context.Context through repeat/poll flows and interactive huh forms (RunWithContext).
  • Introduce ErrInterrupted and switch gh workflow run to context-aware execution (ExecGHContext).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
cmd/gh-aw/main.go Introduces signal-cancelable context and uses ExecuteContext.
pkg/cli/trial_command.go Propagates context into secret prompting and workflow completion wait.
pkg/cli/signal_aware_poll.go Adds Ctx to polling options and introduces ErrInterrupted.
pkg/cli/run_workflow_execution.go Uses context-aware GH subprocess execution; propagates interrupt errors upward.
pkg/cli/run_interactive.go Passes context into interactive forms via RunWithContext.
pkg/cli/retry.go Adds context to repeat options and checks cancellation before each iteration.
pkg/cli/pr_automerge.go Adds context to WaitForWorkflowCompletion and passes it to polling.
pkg/cli/pr_automerge_test.go Updates test for new WaitForWorkflowCompletion(ctx, ...) signature.
pkg/cli/interactive.go Stores context on builder and runs forms with RunWithContext.
pkg/cli/engine_secrets.go Adds context to engine secret prompting and runs forms with RunWithContext.
pkg/cli/add_interactive_orchestrator.go Adds/threads Ctx through interactive add-flow config.
pkg/cli/add_interactive_workflow.go Runs interactive form with context.
pkg/cli/add_interactive_schedule.go Runs interactive form with context.
pkg/cli/add_interactive_engine.go Runs interactive form with context; passes context into secret config.
pkg/cli/add_interactive_auth.go Runs interactive form with context.
Comments suppressed due to low confidence (1)

pkg/cli/retry.go:1

  • ExecuteWithRepeat can only observe cancellation between iterations because ExecuteFunc has no way to receive ctx. As a result, if an iteration blocks inside ExecuteFunc, Ctrl-C won’t stop it until it returns. To make repeat truly cancellation-aware, consider evolving the API to ExecuteFunc func(ctx context.Context) error (or adding an additional ExecuteFuncWithContext field) and pass the ctx through.
package cli

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 125 to 129
return PollWithSignalHandling(PollOptions{
Ctx: ctx,
PollInterval: 10 * time.Second,
Timeout: timeout,
PollFunc: func() (PollResult, error) {
Comment on lines +18 to +19
// ErrInterrupted is returned when polling is interrupted by a signal or context cancellation
var ErrInterrupted = errors.New("interrupted by user")
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in d5b5a76:

  • TestPollWithSignalHandling_ContextCancellation: starts a poll with PollContinue, cancels the context mid-flight, and asserts errors.Is(err, ErrInterrupted)
  • TestPollWithSignalHandling_AlreadyCancelledContext: verifies ErrInterrupted is returned immediately when polling starts with an already-cancelled context
  • TestWaitForWorkflowCompletion_ContextCancellation: verifies that WaitForWorkflowCompletion propagates a cancellation error (ErrInterrupted or context.Canceled) so callers can detect and stop on interruption

Also fixed a CI build failure in the same commit: a previous "Potential fix" commit introduced workflow.RunGHWithContext (undefined) instead of the correct workflow.RunGHContext inside WaitForWorkflowCompletion's PollFunc.

dsyme and others added 5 commits March 19, 2026 16:36
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…llation tests

- Fix undefined workflow.RunGHWithContext → workflow.RunGHContext in
  WaitForWorkflowCompletion's PollFunc (fixes CI build failure)
- Add TestPollWithSignalHandling_ContextCancellation: verifies ErrInterrupted
  returned when context is cancelled mid-poll
- Add TestPollWithSignalHandling_AlreadyCancelledContext: verifies ErrInterrupted
  returned when polling starts with an already-cancelled context
- Add TestWaitForWorkflowCompletion_ContextCancellation: verifies that
  WaitForWorkflowCompletion propagates a cancellation error to callers

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan
Copy link
Contributor

@copilot review comment

…n Ctrl-C

Change PollFunc signature from func() to func(ctx context.Context) so that
poll callbacks receive the same context used by the poll loop. This enables
callers to pass ctx to context-aware operations (e.g. RunGHContext) and abort
mid-call when Ctrl-C is pressed, rather than only aborting between iterations.

- PollOptions.PollFunc: func() → func(ctx context.Context)
- PollWithSignalHandling: pass ctx to both PollFunc call sites
- WaitForWorkflowCompletion: use PollFunc(ctx) directly; remove redundant
  ctx.Done() guard inside closure (now handled by RunGHContext's ctx arg)
- All test PollFuncs updated to new signature

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot review comment

Addressed the remaining open review comment in 27b521c: changed PollFunc from func() (PollResult, error) to func(ctx context.Context) (PollResult, error). The poll loop now passes ctx directly to each PollFunc call, so callbacks can forward it to context-aware operations (like RunGHContext) and abort mid-call on Ctrl-C rather than only between iterations. The redundant manual ctx.Done() guard inside WaitForWorkflowCompletion's closure was removed since RunGHContext handles it directly now.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from pelikhan March 19, 2026 17:11
@pelikhan pelikhan merged commit 1540e14 into main Mar 19, 2026
53 checks passed
@pelikhan pelikhan deleted the copilot/fix-ctrl-c-handling-issues branch March 19, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gh aw has problems with Ctrl-C on WSL

4 participants