summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-22 14:19:15 +0300
committerPaul Buetow <paul@buetow.org>2026-06-22 14:19:15 +0300
commit6fafae88fd8cfcb972c1ed98d8eb591a910043d2 (patch)
treed8176b19fb12b854aaa745dd31c417665762102d
parente03680f7db7e033f7b193dc8880cf1b0bd843bfc (diff)
Fix browser process reaping for mq0
-rw-r--r--internal/ui/keyactions.go6
-rw-r--r--internal/ui/table_test.go66
2 files changed, 69 insertions, 3 deletions
diff --git a/internal/ui/keyactions.go b/internal/ui/keyactions.go
index e8e0c55..ef0189a 100644
--- a/internal/ui/keyactions.go
+++ b/internal/ui/keyactions.go
@@ -122,9 +122,9 @@ func openURLCmd(browserCmd, url string, taskID int) tea.Cmd {
if err := cmd.Start(); err != nil {
return openURLDoneMsg{err: fmt.Errorf("opening browser: %w", err)}
}
- if err := cmd.Process.Release(); err != nil {
- return openURLDoneMsg{err: fmt.Errorf("releasing browser process: %w", err)}
- }
+ go func() {
+ _ = cmd.Wait()
+ }()
return openURLDoneMsg{taskID: taskID}
}
}
diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go
index 065d817..f255fe3 100644
--- a/internal/ui/table_test.go
+++ b/internal/ui/table_test.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"reflect"
"regexp"
+ "runtime"
"strings"
"testing"
"time"
@@ -2715,6 +2716,71 @@ func TestOpenURLCommandDoesNotWaitForBrowserExit(t *testing.T) {
}
}
+func TestOpenURLCommandReapsBrowserProcess(t *testing.T) {
+ if runtime.GOOS != "linux" {
+ t.Skip("requires /proc process state")
+ }
+
+ tmp := t.TempDir()
+ taskPath := filepath.Join(tmp, "task")
+ pidPath := filepath.Join(tmp, "browser.pid")
+ browserPath := filepath.Join(tmp, "browser")
+
+ taskScript := "#!/bin/sh\n" +
+ "if echo \"$@\" | grep -q export; then\n" +
+ " echo '{\"id\":1,\"uuid\":\"1\",\"description\":\"alpha https://example.com\",\"status\":\"pending\",\"entry\":\"\",\"priority\":\"\",\"urgency\":0}'\n" +
+ " exit 0\n" +
+ "fi\n"
+ if err := os.WriteFile(taskPath, []byte(taskScript), 0o755); err != nil {
+ t.Fatal(err)
+ }
+
+ browserScript := "#!/bin/sh\n" +
+ "echo $$ > " + pidPath + "\n"
+ if err := os.WriteFile(browserPath, []byte(browserScript), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ setupEnv(t, taskPath)
+
+ m, err := New(nil, browserPath)
+ if err != nil {
+ t.Fatalf("New: %v", err)
+ }
+
+ mv, cmd := (&m).Update(tea.KeyPressMsg{Code: 'o', Text: "o"})
+ if cmd == nil {
+ t.Fatalf("open URL unexpectedly returned no command")
+ }
+ m = *mv.(*Model)
+
+ msg := cmd()
+ done, ok := msg.(openURLDoneMsg)
+ if !ok {
+ t.Fatalf("open URL command returned %T, want openURLDoneMsg", msg)
+ }
+ if done.err != nil {
+ t.Fatalf("open URL command failed: %v", done.err)
+ }
+
+ if !waitForFile(pidPath, 2*time.Second) {
+ t.Fatalf("browser pid file was not written")
+ }
+ data, err := os.ReadFile(pidPath)
+ if err != nil {
+ t.Fatalf("read browser pid: %v", err)
+ }
+ procPath := filepath.Join("/proc", strings.TrimSpace(string(data)))
+
+ deadline := time.Now().Add(2 * time.Second)
+ for time.Now().Before(deadline) {
+ if _, err := os.Stat(procPath); errors.Is(err, os.ErrNotExist) {
+ return
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ t.Fatalf("browser process still exists at %s; child may not have been reaped", procPath)
+}
+
func TestUltraReloadPreservesFilteredSelection(t *testing.T) {
tmp := t.TempDir()
taskPath, phaseFile := setupUltraReloadTaskSet(t, tmp)