chenhg5/cc-connect · critical
process tree (pid %d) still alive after SIGKILL retries: %w
Error message
process tree (pid %d) still alive after SIGKILL retries: %w
What it means
Close attempts a graceful shutdown then SIGKILL of the claude process tree; if the tree is still alive 10 seconds after SIGKILL was reported successful, it returns 'process tree (pid %d) still alive after SIGKILL retries: %w' (wrapping the kill error) or the non-wrapped variant. This signals an orphaned/unkillable process — typically in uninterruptible sleep (D state) or a kill-permission problem.
Source
Thrown at agent/claudecode/session.go:1309
"attempt", attempt, "max_attempts", killAttempts, "error", killErr)
select {
case <-cs.done:
slog.Info("claudeSession: exited during force-kill retries")
return nil
case <-time.After(2 * time.Second):
}
}
select {
case <-cs.done:
return nil
case <-time.After(10 * time.Second):
pid := -1
if cs.cmd != nil && cs.cmd.Process != nil {
pid = cs.cmd.Process.Pid
}
if killErr != nil {
return fmt.Errorf("process tree (pid %d) still alive after SIGKILL retries: %w", pid, killErr)
}
return fmt.Errorf("process tree (pid %d) still alive 10s after SIGKILL reported success", pid)
}
}
// shellJoinArgs joins args into a single string, quoting any arg that
// contains whitespace so that a shell-style splitter (like my_cli's
// splitCommandLine) preserves each arg as one token.
//
// Uses single quotes because some splitters (e.g. my_cli) don't support
// backslash escapes inside double quotes. For values containing single
// quotes, we close the single-quoted segment, add an escaped single
// quote, and reopen: 'it'\”s' → it's
func shellJoinArgs(args []string) string {
var b strings.Builder
for i, a := range args {
if i > 0 {
b.WriteByte(' ')View on GitHub (pinned to 4000b2338a)
Solutions
- Check process state: `ps -o stat= -p <pid>` — 'D' means uninterruptible I/O; fix the I/O target (unmount/recover NFS) and the process will die
- Kill the whole tree manually: `pkill -9 -P <pid>; kill -9 <pid>`, or use cgroup/pid-namespace kill (systemd-run --scope, kill all in cgroup)
- Read the wrapped killErr — EPERM means the daemon user lacks permission for that pid (different user/namespace); run cc-connect as the same user or adjust limits
- Prevent recurrence: keep claude CLI and its tool subprocesses signal-friendly; consider process-group setup (Setpgid) so Close can kill -PGID
Example fix
// before
cmd.SysProcAttr = &syscall.SysProcAttr{} // child may survive in its own group
// after
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Close: syscall.Kill(-cs.cmd.Process.Pid, syscall.SIGKILL) // kill whole group Defensive patterns
Strategy: retry
Validate before calling
// detect unkillable processes early, before Close times out
func processState(pid int) string {
b, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil { return "gone" }
fields := strings.Fields(string(b))
if len(fields) > 2 { return fields[2] } // R,S,D,Z,T — 'D' = unkillable I/O wait
return "unknown"
} Try / catch
if err := sess.Close(); err != nil {
if strings.Contains(err.Error(), "still alive after SIGKILL") {
// extract pid from message or track it; kill whole tree manually
exec.Command("pkill", "-9", "-P", pidStr).Run()
exec.Command("kill", "-9", pidStr).Run()
log.Error("orphaned claude process tree", "pid", pidStr)
}
} Prevention
- Spawn claude with Setpgid and kill the negative process-group pid so tool subprocesses die too
- Avoid running sessions against NFS/FUSE working directories where D-state hangs are common
- Run cc-connect and the claude CLI under the same user/namespace so SIGKILL is permitted
- Monitor for orphaned claude processes (ps/cgroup accounting) and alert on accumulation
When it happens
Trigger: Close → wait with 10s timeout expires while the process tree (children spawned by claude, e.g. shell tools) survives: process stuck in D state on I/O/NFS, zombie children reparented, kill targeting the wrong pid, or killErr (operation not permitted) from the OS.
Common situations: Claude Code spawned shell/tool subprocesses that ignore signals; processes blocked on NFS/FUSE I/O (D state, immune to SIGKILL); container pid-namespace restrictions; runaway sessions accumulating orphaned claude processes.
Related errors
- process tree (pid %d) still alive 10s after SIGKILL reported
- %s
- session process is not running
- write stdin: %w
- session file not found: %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/5d318a2990f6c95c.
Report an issue: GitHub.