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

  1. 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
  2. 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)
  3. 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
  4. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/5d318a2990f6c95c. Report an issue: GitHub.