detached 798174c

Fix telegram bot process checks on Windows

commit
798174c22c9110b1247a4f971eae2548f8e8450d
author
patriceckhart · · 2026-06-13 15:39 UTC
parent
7f954ce
signature
unsigned
refs
tag: v0.2.30
3 files · +101 -33
M packages/agent/modes/telegram/daemon.go +5 -33
@@ -7,7 +7,6 @@ import (
77 "path/filepath"
88 "strconv"
99 "strings"
10- "syscall"
1110 "time"
1211 )
1312
@@ -68,42 +67,15 @@ func IsRunning(zotHome string) (int, bool, error) {
6867 if pid <= 0 {
6968 return 0, false, nil
7069 }
71- proc, err := os.FindProcess(pid)
70+ alive, err := processAlive(pid)
7271 if err != nil {
7372 return pid, false, nil
7473 }
75- // signal 0 is POSIX's "does the process exist?" probe. On Windows
76- // os.Process is always usable and Signal(0) returns nil, so we'd
77- // miss stale pids; acceptable for the macos/linux-first audience.
78- if err := proc.Signal(syscall.Signal(0)); err != nil {
79- if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
80- return pid, false, nil
81- }
82- // Other errors (EPERM) mean the process exists but we can't
83- // inspect it; treat as running.
84- return pid, true, nil
85- }
86- return pid, true, nil
74+ return pid, alive, nil
8775 }
8876
89-// StopProcess sends SIGTERM to pid and waits up to graceful for it to
90-// exit, then escalates to SIGKILL. Returns nil if the process is gone.
77+// StopProcess asks pid to exit and waits up to graceful for it to stop,
78+// then escalates to a forced kill. Returns nil if the process is gone.
9179 func StopProcess(pid int, graceful time.Duration) error {
92- proc, err := os.FindProcess(pid)
93- if err != nil {
94- return err
95- }
96- _ = proc.Signal(syscall.SIGTERM)
97-
98- deadline := time.Now().Add(graceful)
99- for time.Now().Before(deadline) {
100- if err := proc.Signal(syscall.Signal(0)); err != nil {
101- if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
102- return nil
103- }
104- }
105- time.Sleep(100 * time.Millisecond)
106- }
107- _ = proc.Kill()
108- return nil
80+ return stopProcess(pid, graceful)
10981 }
@@ -7,7 +7,6 @@ import (
7 "path/filepath"7 "path/filepath"
8 "strconv"8 "strconv"
9 "strings"9 "strings"
10- "syscall"
11 "time"10 "time"
12 )11 )
13 12
@@ -68,42 +67,15 @@ func IsRunning(zotHome string) (int, bool, error) {
68 if pid <= 0 {67 if pid <= 0 {
69 return 0, false, nil68 return 0, false, nil
70 }69 }
71- proc, err := os.FindProcess(pid)70+ alive, err := processAlive(pid)
72 if err != nil {71 if err != nil {
73 return pid, false, nil72 return pid, false, nil
74 }73 }
75- // signal 0 is POSIX's "does the process exist?" probe. On Windows74+ return pid, alive, nil
76- // os.Process is always usable and Signal(0) returns nil, so we'd
77- // miss stale pids; acceptable for the macos/linux-first audience.
78- if err := proc.Signal(syscall.Signal(0)); err != nil {
79- if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
80- return pid, false, nil
81- }
82- // Other errors (EPERM) mean the process exists but we can't
83- // inspect it; treat as running.
84- return pid, true, nil
85- }
86- return pid, true, nil
87 }75 }
88 76
89-// StopProcess sends SIGTERM to pid and waits up to graceful for it to77+// StopProcess asks pid to exit and waits up to graceful for it to stop,
90-// exit, then escalates to SIGKILL. Returns nil if the process is gone.78+// then escalates to a forced kill. Returns nil if the process is gone.
91 func StopProcess(pid int, graceful time.Duration) error {79 func StopProcess(pid int, graceful time.Duration) error {
92- proc, err := os.FindProcess(pid)80+ return stopProcess(pid, graceful)
93- if err != nil {
94- return err
95- }
96- _ = proc.Signal(syscall.SIGTERM)
97-
98- deadline := time.Now().Add(graceful)
99- for time.Now().Before(deadline) {
100- if err := proc.Signal(syscall.Signal(0)); err != nil {
101- if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
102- return nil
103- }
104- }
105- time.Sleep(100 * time.Millisecond)
106- }
107- _ = proc.Kill()
108- return nil
109 }81 }
A packages/agent/modes/telegram/process_unix.go +44 -0
@@ -0,0 +1,44 @@
1+//go:build !windows
2+
3+package telegram
4+
5+import (
6+ "errors"
7+ "os"
8+ "syscall"
9+ "time"
10+)
11+
12+func processAlive(pid int) (bool, error) {
13+ proc, err := os.FindProcess(pid)
14+ if err != nil {
15+ return false, err
16+ }
17+ if err := proc.Signal(syscall.Signal(0)); err != nil {
18+ if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
19+ return false, nil
20+ }
21+ // Other errors (EPERM) mean the process exists but we can't inspect it.
22+ return true, nil
23+ }
24+ return true, nil
25+}
26+
27+func stopProcess(pid int, graceful time.Duration) error {
28+ proc, err := os.FindProcess(pid)
29+ if err != nil {
30+ return err
31+ }
32+ _ = proc.Signal(syscall.SIGTERM)
33+
34+ deadline := time.Now().Add(graceful)
35+ for time.Now().Before(deadline) {
36+ alive, err := processAlive(pid)
37+ if err != nil || !alive {
38+ return nil
39+ }
40+ time.Sleep(100 * time.Millisecond)
41+ }
42+ _ = proc.Kill()
43+ return nil
44+}
@@ -0,0 +1,44 @@
1+//go:build !windows
2+
3+package telegram
4+
5+import (
6+ "errors"
7+ "os"
8+ "syscall"
9+ "time"
10+)
11+
12+func processAlive(pid int) (bool, error) {
13+ proc, err := os.FindProcess(pid)
14+ if err != nil {
15+ return false, err
16+ }
17+ if err := proc.Signal(syscall.Signal(0)); err != nil {
18+ if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
19+ return false, nil
20+ }
21+ // Other errors (EPERM) mean the process exists but we can't inspect it.
22+ return true, nil
23+ }
24+ return true, nil
25+}
26+
27+func stopProcess(pid int, graceful time.Duration) error {
28+ proc, err := os.FindProcess(pid)
29+ if err != nil {
30+ return err
31+ }
32+ _ = proc.Signal(syscall.SIGTERM)
33+
34+ deadline := time.Now().Add(graceful)
35+ for time.Now().Before(deadline) {
36+ alive, err := processAlive(pid)
37+ if err != nil || !alive {
38+ return nil
39+ }
40+ time.Sleep(100 * time.Millisecond)
41+ }
42+ _ = proc.Kill()
43+ return nil
44+}
A packages/agent/modes/telegram/process_windows.go +52 -0
@@ -0,0 +1,52 @@
1+//go:build windows
2+
3+package telegram
4+
5+import (
6+ "errors"
7+ "os"
8+ "time"
9+
10+ "golang.org/x/sys/windows"
11+)
12+
13+const stillActive = 259
14+
15+func processAlive(pid int) (bool, error) {
16+ h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
17+ if err != nil {
18+ if errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
19+ return false, nil
20+ }
21+ if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
22+ return true, nil
23+ }
24+ return false, err
25+ }
26+ defer windows.CloseHandle(h)
27+
28+ var code uint32
29+ if err := windows.GetExitCodeProcess(h, &code); err != nil {
30+ return false, err
31+ }
32+ return code == stillActive, nil
33+}
34+
35+func stopProcess(pid int, graceful time.Duration) error {
36+ proc, err := os.FindProcess(pid)
37+ if err != nil {
38+ return err
39+ }
40+ _ = proc.Signal(os.Interrupt)
41+
42+ deadline := time.Now().Add(graceful)
43+ for time.Now().Before(deadline) {
44+ alive, err := processAlive(pid)
45+ if err != nil || !alive {
46+ return nil
47+ }
48+ time.Sleep(100 * time.Millisecond)
49+ }
50+ _ = proc.Kill()
51+ return nil
52+}
@@ -0,0 +1,52 @@
1+//go:build windows
2+
3+package telegram
4+
5+import (
6+ "errors"
7+ "os"
8+ "time"
9+
10+ "golang.org/x/sys/windows"
11+)
12+
13+const stillActive = 259
14+
15+func processAlive(pid int) (bool, error) {
16+ h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
17+ if err != nil {
18+ if errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
19+ return false, nil
20+ }
21+ if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
22+ return true, nil
23+ }
24+ return false, err
25+ }
26+ defer windows.CloseHandle(h)
27+
28+ var code uint32
29+ if err := windows.GetExitCodeProcess(h, &code); err != nil {
30+ return false, err
31+ }
32+ return code == stillActive, nil
33+}
34+
35+func stopProcess(pid int, graceful time.Duration) error {
36+ proc, err := os.FindProcess(pid)
37+ if err != nil {
38+ return err
39+ }
40+ _ = proc.Signal(os.Interrupt)
41+
42+ deadline := time.Now().Add(graceful)
43+ for time.Now().Before(deadline) {
44+ alive, err := processAlive(pid)
45+ if err != nil || !alive {
46+ return nil
47+ }
48+ time.Sleep(100 * time.Millisecond)
49+ }
50+ _ = proc.Kill()
51+ return nil
52+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close