chenhg5/cc-connect · error

taskkill failed: %w: %s; process kill fallback failed: %w

Error message

taskkill failed: %w: %s; process kill fallback failed: %w

What it means

forceKillCmd (agent/claudecode/proc_windows.go:69) hard-kills the whole descendant tree with 'taskkill /T /F'. If taskkill fails (and it isn't the benign not-running case), it falls back to cmd.Process.Kill(); if that also fails, it reports 'taskkill failed: %w: %s; process kill fallback failed: %w' containing both the taskkill error/output and the Kill error. This means the Claude Code process tree could not be force-terminated and may still be running.

Source

Thrown at agent/claudecode/proc_windows.go:69

}

// forceKillCmd taskkill /T /F's the entire descendant tree rooted at cmd.
func forceKillCmd(cmd *exec.Cmd) error {
	if cmd == nil || cmd.Process == nil {
		return nil
	}
	killCmd := exec.Command("taskkill", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
	output, err := killCmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if isTaskkillNotRunning(output) {
		return nil
	}
	if killErr := cmd.Process.Kill(); killErr == nil || errors.Is(killErr, os.ErrProcessDone) {
		return nil
	} else {
		return fmt.Errorf("taskkill failed: %w: %s; process kill fallback failed: %w", err, processKillOutput(output), killErr)
	}
}

func isTaskkillNotRunning(output []byte) bool {
	lower := bytes.ToLower(output)
	return bytes.Contains(lower, []byte("there is no running instance")) ||
		bytes.Contains(lower, []byte("not found"))
}

func processKillOutput(output []byte) string {
	trimmed := strings.TrimSpace(string(output))
	if trimmed == "" {
		return "(empty output)"
	}
	return trimmed
}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check both embedded errors: if the process actually exited (ErrProcessDone), treat the stop as successful.
  2. Ensure cc-connect runs as the same user (or an administrator) that spawned the Claude Code process.
  3. Manually verify with 'tasklist | findstr claude' whether the process is still alive; kill by PID if needed.
  4. If EDR/AV blocks taskkill, add an exclusion or fall back to terminating the process group via Windows job objects.

Example fix

// before
if err := session.Stop(ctx); err != nil {
    panic(err)
}
// after
if err := session.Stop(ctx); err != nil {
    if errors.Is(err, os.ErrProcessDone) {
        return nil // already dead
    }
    log.Warn("force kill failed; process may still be running", "err", err)
    // optionally: exec taskkill /F /PID <pid> manually and re-check
}
Defensive patterns

Strategy: fallback

Try / catch

if err := session.Stop(ctx); err != nil {
    if errors.Is(err, os.ErrProcessDone) {
        return nil
    }
    // last-resort: kill by PID recorded at spawn time
    if pid := session.PID(); pid > 0 {
        exec.Command("taskkill", "/F", "/PID", strconv.Itoa(pid)).Run()
    }
}

Prevention

When it happens

Trigger: Calling Stop (force kill path) on Windows where both taskkill /T /F fails with non-not-running output and cmd.Process.Kill() returns an error other than os.ErrProcessDone.

Common situations: Killing a process started by a different user (access denied for both taskkill and Kill); process protected by AV/EDR tooling; already-exited process whose handle state confuses Kill; restricted service accounts (e.g. SYSTEM vs user session) in daemon deployments.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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