Skip to content

feat(console): handle huh.ErrUserAborted as graceful exit (exit code 130)#44698

Merged
pelikhan merged 3 commits into
mainfrom
copilot/go-fan-review-huh-module
Jul 10, 2026
Merged

feat(console): handle huh.ErrUserAborted as graceful exit (exit code 130)#44698
pelikhan merged 3 commits into
mainfrom
copilot/go-fan-review-huh-module

Conversation

Copilot AI commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

No call site in the codebase checked huh.ErrUserAborted, so Ctrl-C during secret entry surfaced as a hard failure (e.g. failed to get Copilot token: user aborted) instead of a clean cancellation.

Changes

  • pkg/console/prompt_form.go — adds IsCancelled(err error) bool wrapping errors.Is(err, huh.ErrUserAborted), centralising the check alongside the other form helpers.

  • pkg/cli/engine_secrets.go — adds a private promptCancelled() helper (prints "Cancelled." to stderr, returns ExitCodeError{130}); all three secret-entry call sites (promptForCopilotTokenUnified, promptForSystemTokenUnified, promptForGenericAPIKeyUnified) now call it instead of wrapping the error as a failure.

  • pkg/cli/run_interactive.goconfirmExecution now logs "User aborted confirmation" vs "Confirmation failed: <err>" to distinguish the two paths.

  • pkg/console/prompt_form_test.go — unit tests for IsCancelled covering direct, wrapped, unrelated, and nil errors.

// Before: Ctrl-C printed an error and exited 1
if err := form.RunWithContext(config.ctx()); err != nil {
    return fmt.Errorf("failed to get Copilot token: %w", err)
}

// After: Ctrl-C prints "Cancelled." and exits 130
if err := form.RunWithContext(config.ctx()); err != nil {
    if console.IsCancelled(err) {
        return promptCancelled()
    }
    return fmt.Errorf("failed to get Copilot token: %w", err)
}

Copilot AI linked an issue Jul 10, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits July 10, 2026 10:49
…acefully

- Add console.IsCancelled(err) helper to pkg/console/prompt_form.go that
  wraps errors.Is(err, huh.ErrUserAborted) for detecting deliberate user
  cancellation (Ctrl-C / Esc before form submission).
- Update the 3 secret-entry call sites in pkg/cli/engine_secrets.go to
  check IsCancelled: on user abort, print "Cancelled." and return
  ExitCodeError{Code: 130} instead of a hard failure message.
- Update pkg/cli/run_interactive.go confirmExecution to log "User aborted
  confirmation" vs "Confirmation failed" to distinguish the two paths.
- Add TestIsCancelled unit tests covering ErrUserAborted, wrapped errors,
  other errors, and nil.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ngine_secrets.go

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Review usage of huh module in gh-aw feat(console): handle huh.ErrUserAborted as graceful exit (exit code 130) Jul 10, 2026
Copilot AI requested a review from pelikhan July 10, 2026 10:52
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category feature
Risk 🟡 Medium
Score 50/100 (impact:20 + urgency:18 + quality:12)
Action batch_review

Handles huh.ErrUserAborted as graceful exit with code 130 in the console module. 4 files, +51 lines. Behavioral change to exit code handling — needs review.

Generated by 🔧 PR Triage Agent · 206.8 AIC · ⌖ 8.74 AIC · ⊞ 5.4K ·

@pelikhan pelikhan marked this pull request as ready for review July 10, 2026 14:43
Copilot AI review requested due to automatic review settings July 10, 2026 14:43
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #44698 does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (51 additions detected, threshold is 100).

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Copilot AI left a comment

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.

Pull request overview

This PR improves interactive UX by treating huh.ErrUserAborted (e.g., Ctrl-C/Esc during huh prompts) as an intentional cancellation, enabling graceful handling (including exit code 130 for secret-entry prompts) rather than surfacing it as a hard failure.

Changes:

  • Added console.IsCancelled(err) to centralize detection of huh.ErrUserAborted.
  • Updated interactive secret-entry prompts to print Cancelled. and return ExitCodeError{Code: 130} when the user aborts.
  • Updated interactive run confirmation logging to distinguish user-abort from genuine confirmation failures.
  • Added unit tests covering IsCancelled for direct, wrapped, unrelated, and nil errors.
Show a summary per file
File Description
pkg/console/prompt_form.go Adds IsCancelled helper wrapping errors.Is(err, huh.ErrUserAborted) for centralized cancellation detection.
pkg/console/prompt_form_test.go Adds unit tests validating IsCancelled behavior across common error shapes (direct, wrapped, nil, unrelated).
pkg/cli/engine_secrets.go Handles prompt aborts by printing Cancelled. and returning ExitCodeError{130} to produce a clean cancellation path.
pkg/cli/run_interactive.go Improves logging by separating “user aborted confirmation” from other confirmation errors.

Review details

Tip

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

  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Low

@github-actions github-actions Bot mentioned this pull request Jul 10, 2026

@github-actions github-actions Bot left a comment

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.

The changes look correct and well-structured.\n\n- IsCancelled correctly uses errors.Is to handle wrapped errors.\n- promptCancelled() follows Unix convention (exit 130 for Ctrl-C).\n- All three secret-entry call sites are updated consistently.\n- Tests cover direct, wrapped, unrelated, and nil cases.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 12.6 AIC · ⌖ 4.94 AIC · ⊞ 4.8K

@github-actions github-actions Bot left a comment

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.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — one observation worth addressing, rest is solid.

📋 Key Themes & Highlights

Key Themes

  • Inconsistent exit-code propagation in confirmExecution: The three secret-prompt call sites now return ExitCodeError{130} on Ctrl-C, but confirmExecution still returns (false, nil). A caller cannot distinguish "user said no" from "user pressed Ctrl-C", so the process will exit 1 instead of 130 when aborting a workflow confirmation.

Positive Highlights

  • ✅ Centralised IsCancelled() helper with correct errors.Is wrapping — easy to reuse.
  • ✅ Comprehensive test suite covers wrapped, unrelated, and nil error cases.
  • ✅ Consistent application across all three secret-entry prompts.
  • ✅ Exit code 130 matches the Unix convention for Ctrl-C terminated processes.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 21.6 AIC · ⌖ 4.46 AIC · ⊞ 6.6K
Comment /matt to run again

}
return false
}

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.

[/diagnosing-bugs] confirmExecution still returns false on Ctrl-C — the exit-code-130 signal isn't propagated here, unlike the three secret-prompt call sites. A caller that treats false as 'user said no' will exit 1, not 130.

💡 Suggested fix

Consider changing the signature to confirmExecution(...) (bool, error) and returning an error on abort so the caller can propagate ExitCodeError{130}:

if console.IsCancelled(err) {
    runInteractiveLog.Print("User aborted confirmation")
    return false, promptCancelled()
}

This makes cancellation handling consistent across all interactive prompts.

@copilot please address this.

@github-actions github-actions Bot left a comment

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.

REQUEST_CHANGES — The cancellation-handling intent is correct, but the implementation has gaps that make the exit-130 contract incomplete.

Blocking issues (3)

confirmExecution doesn't propagate exit-130

The bool return type loses the cancellation signal — callers exit 0 on Ctrl-C during confirmation. The secret-entry paths exit 130; the confirmation path silently exits 0. This is the PR's stated goal not fully achieved.

promptCancelled() is untested

The function that defines the observable contract (exit code 130 + "Cancelled." stderr) has zero test coverage. A refactor can silently break the exit code.

Possible double output from huh + promptCancelled()

Needs empirical verification — if huh already renders a cancellation message before returning ErrUserAborted, the extra fmt.Fprintln produces confusing double output.

🔎 Code quality review by PR Code Quality Reviewer · 49.6 AIC · ⌖ 4.77 AIC · ⊞ 5.4K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/run_interactive.go:63

confirmExecution cannot propagate exit-130 on Ctrl-C: the bool return collapses three outcomes (confirmed, denied, cancelled) into two, so callers cannot distinguish cancellation from a deliberate "No".

<details>
<summary>💡 Suggested fix</summary>

Change the signature to (bool, error) and return the ExitCodeError{130} on cancellation:

func confirmExecution(ctx context.Context, wf *WorkflowOption, inputs []string) (bool, error) {
    ...
    if err := form.RunWithContext(ct</details>

<details><summary>pkg/cli/engine_secrets.go:27</summary>

**Possible double &quot;Cancelled.&quot; output**: `promptCancelled()` unconditionally prints to stderr, but the `huh` library itself may already render a cancellation indicator in the terminal when `ErrUserAborted` is returned.

&lt;details&gt;
&lt;summary&gt;💡 Details&lt;/summary&gt;

Depending on the `huh` theme and version in use, pressing Ctrl-C may already display something like `^C` or a styled cancellation message in the TUI before returning `ErrUserAborted`. Printing an additional `&quot;Cancelled.&quot;` line on top of t</details>

<details><summary>pkg/cli/engine_secrets.go:24</summary>

**`promptCancelled()` is untested**: the most behavior-critical helper in this PRit writes to stderr and returns `ExitCodeError{Code: 130}`has no unit test.

&lt;details&gt;
&lt;summary&gt;💡 What to test&lt;/summary&gt;

Add a test alongside `TestIsCancelled` (in `prompt_form_test.go`, or a new file if `promptCancelled` stays in `engine_secrets.go`) that verifies:

1. The returned error is an `*ExitCodeError` with `Code == 130`.
2. `&quot;Cancelled.\n&quot;` is written to stderr.

```go
func TestPromptCancelled(t *</details>

<details><summary>pkg/cli/run_interactive.go:61</summary>

**UX inconsistency: Ctrl-C during confirmation is silent**: secret-entry prompts print `&quot;Cancelled.&quot;` to stderr via `promptCancelled()`, but aborting the confirmation step only logs internallythe user sees nothing.

&lt;details&gt;
&lt;summary&gt;💡 Details&lt;/summary&gt;

In `engine_secrets.go`, every `IsCancelled` branch calls `promptCancelled()`, which writes `&quot;Cancelled.&quot;` to stderr. In `run_interactive.go`, the cancellation branch only calls `runInteractiveLog.Print(...)`, which goes to the debug log — …

</details>

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100 — Excellent

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 4 table-driven via t.Run)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 4 (100%)
Duplicate clusters 0
Inflation Yes (2.33:1, threshold 2:1)
🚨 Violations 0
Test Classification Coverage
TestIsCancelled/returns true for huh.ErrUserAborted design_test Direct error sentinel ✅
TestIsCancelled/returns true for wrapped huh.ErrUserAborted design_test Wrapped error edge case ✅
TestIsCancelled/returns false for other errors design_test Error discrimination ✅
TestIsCancelled/returns false for nil design_test Nil-safety edge case ✅
📝 Analysis

Test Inflation Note (2.33:1 ratio)

  • Test file: +21 lines | Production: +9 lines
  • Status: Justified ✅
    • New public API (IsCancelled) requires comprehensive edge-case testing
    • Table-driven pattern via t.Run() is the right design choice
    • Covers wrapped errors, nil-safety, and error discrimination — all necessary for correctness

Assertion Quality

  • All 4 assertions use require.* (testify/require)
  • All test design patterns and behavioral contracts verified
  • Zero implementation leakage

Build Tag

  • ✅ File has required //go:build !integration && !js && !wasm on line 1

Verdict

Approved. 0% implementation tests (threshold: 30%). All tests verify behavioral contracts; zero mock violations; 100% edge-case coverage for the new IsCancelled API.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 20.4 AIC · ⌖ 11.2 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

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.

✅ Test Quality Sentinel: 90/100. 0% implementation tests (threshold: 30%). All tests verify behavioral contracts for the new IsCancelled API with comprehensive edge-case coverage (wrapped errors, nil-safety, error discrimination).

@pelikhan pelikhan merged commit d3c1298 into main Jul 10, 2026
83 of 96 checks passed
@pelikhan pelikhan deleted the copilot/go-fan-review-huh-module branch July 10, 2026 15:14
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved review feedback (newest first):

Generated by 👨‍🍳 PR Sous Chef · 11.3 AIC · ⌖ 6.74 AIC · ⊞ 4.7K ·
Comment /souschef to run again

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[go-fan] Go Module Review: charm.land/huh/v2

4 participants