From ea8e8922fbe6cac53b596115f890095588a4dad4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:42:21 +0000 Subject: [PATCH 1/3] Initial plan From 269c35f634afaa38b6769ddef0b19c901323810a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:57:18 +0000 Subject: [PATCH 2/3] fix(wasm): recover panic in compileWorkflow goroutine Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- cmd/gh-aw-wasm/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/gh-aw-wasm/main.go b/cmd/gh-aw-wasm/main.go index 8adf27c6c2c..c90b2cd0514 100644 --- a/cmd/gh-aw-wasm/main.go +++ b/cmd/gh-aw-wasm/main.go @@ -3,6 +3,7 @@ package main import ( + "fmt" "strings" "syscall/js" @@ -47,6 +48,11 @@ func compileWorkflow(this js.Value, args []js.Value) any { go func() { defer handler.Release() + defer func() { + if r := recover(); r != nil { + reject.Invoke(js.Global().Get("Error").New(fmt.Sprintf("compileWorkflow panic: %v", r))) + } + }() result, err := doCompile(markdown, files, filename) if err != nil { From fa765f0b3c31ad82a24c3e350f4e393057f0400f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 21:40:15 +0000 Subject: [PATCH 3/3] test(wasm): cover panic recovery and include stack trace Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- cmd/gh-aw-wasm/compile_recovery.go | 25 +++++++++ cmd/gh-aw-wasm/compile_recovery_test.go | 71 +++++++++++++++++++++++++ cmd/gh-aw-wasm/main.go | 24 ++++----- 3 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 cmd/gh-aw-wasm/compile_recovery.go create mode 100644 cmd/gh-aw-wasm/compile_recovery_test.go diff --git a/cmd/gh-aw-wasm/compile_recovery.go b/cmd/gh-aw-wasm/compile_recovery.go new file mode 100644 index 00000000000..c6a13430ef9 --- /dev/null +++ b/cmd/gh-aw-wasm/compile_recovery.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "runtime/debug" +) + +func compileWorkflowPanicError(r any) error { + return fmt.Errorf("compileWorkflow panic: %v\n%s", r, debug.Stack()) +} + +func runCompileWithRecovery(doCompile func() (any, error), resolve func(any), reject func(error)) { + defer func() { + if r := recover(); r != nil { + reject(compileWorkflowPanicError(r)) + } + }() + + result, err := doCompile() + if err != nil { + reject(err) + return + } + resolve(result) +} diff --git a/cmd/gh-aw-wasm/compile_recovery_test.go b/cmd/gh-aw-wasm/compile_recovery_test.go new file mode 100644 index 00000000000..6ee97f4e689 --- /dev/null +++ b/cmd/gh-aw-wasm/compile_recovery_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "errors" + "strings" + "testing" +) + +func TestRunCompileWithRecoverySuccess(t *testing.T) { + var resolved any + var rejected error + + runCompileWithRecovery( + func() (any, error) { return "ok", nil }, + func(result any) { resolved = result }, + func(err error) { rejected = err }, + ) + + if rejected != nil { + t.Fatalf("expected no rejection, got %v", rejected) + } + if resolved != "ok" { + t.Fatalf("expected resolve value %q, got %v", "ok", resolved) + } +} + +func TestRunCompileWithRecoveryError(t *testing.T) { + want := errors.New("compile failed") + var resolved any + var rejected error + + runCompileWithRecovery( + func() (any, error) { return nil, want }, + func(result any) { resolved = result }, + func(err error) { rejected = err }, + ) + + if resolved != nil { + t.Fatalf("expected no resolve value, got %v", resolved) + } + if !errors.Is(rejected, want) { + t.Fatalf("expected rejection %v, got %v", want, rejected) + } +} + +func TestRunCompileWithRecoveryPanic(t *testing.T) { + var resolved any + var rejected error + + runCompileWithRecovery( + func() (any, error) { + panic("boom") + }, + func(result any) { resolved = result }, + func(err error) { rejected = err }, + ) + + if resolved != nil { + t.Fatalf("expected no resolve value, got %v", resolved) + } + if rejected == nil { + t.Fatal("expected rejection from panic, got nil") + } + message := rejected.Error() + if !strings.Contains(message, "compileWorkflow panic: boom") { + t.Fatalf("expected panic prefix in rejection, got %q", message) + } + if !strings.Contains(message, "TestRunCompileWithRecoveryPanic") { + t.Fatalf("expected stack trace in rejection, got %q", message) + } +} diff --git a/cmd/gh-aw-wasm/main.go b/cmd/gh-aw-wasm/main.go index c90b2cd0514..29859e02fb5 100644 --- a/cmd/gh-aw-wasm/main.go +++ b/cmd/gh-aw-wasm/main.go @@ -3,7 +3,6 @@ package main import ( - "fmt" "strings" "syscall/js" @@ -48,18 +47,17 @@ func compileWorkflow(this js.Value, args []js.Value) any { go func() { defer handler.Release() - defer func() { - if r := recover(); r != nil { - reject.Invoke(js.Global().Get("Error").New(fmt.Sprintf("compileWorkflow panic: %v", r))) - } - }() - - result, err := doCompile(markdown, files, filename) - if err != nil { - reject.Invoke(js.Global().Get("Error").New(err.Error())) - return - } - resolve.Invoke(result) + runCompileWithRecovery( + func() (any, error) { + return doCompile(markdown, files, filename) + }, + func(result any) { + resolve.Invoke(result) + }, + func(err error) { + reject.Invoke(js.Global().Get("Error").New(err.Error())) + }, + ) }() return nil