github/copilot-sdk · warning

failed to kill CLI process

Error message

failed to kill CLI process: %w

What it means

When stopping or force-stopping the client, the library kills the tracked CLI OS process. If os.Process.Kill() returns an error (process already finished with an OS error, permission problem, or handle issues), this error wraps it. It means cleanup of the child process did not complete cleanly.

Solutions

  1. Ignore 'process already finished' style errors — the process is gone, which is the goal; wrap Stop in a tolerated check.
  2. Avoid concurrent Stop calls; serialize with your own mutex or use the client's forceStop path only once.
  3. Check permissions in containers/restricted environments (signal rights, PID namespaces).
  4. If the process is stuck in D state, inspect the OS (dmesg) rather than retrying the kill.

Example fix

// before
if err := client.Stop(ctx); err != nil { return err }
// after
if err := client.Stop(ctx); err != nil {
    if !strings.Contains(err.Error(), "process already finished") {
        return err
    }
}
Defensive patterns

Strategy: fallback

Try / catch

if err := client.Stop(ctx); err != nil {
    if strings.Contains(err.Error(), "process already finished") {
        return nil // process is gone; stop goal achieved
    }
    return err
}

Prevention

When it happens

Trigger: Calling client Stop/forceStop when the OS refuses the kill signal — e.g. the process already exited and was reaped unexpectedly, or the process runs as a different user.

Common situations: Double-stop from concurrent callers, killing a process owned by another user or in an uninterruptible state, container environments restricting signals.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/f871e2f752d9cfa4. Report an issue: GitHub.

Appendix: source

Thrown at go/client.go:2364

		environment["COPILOT_DISABLE_KEYTAR"] = "1"
	}

	return inProcessHostConfig{
		Environment: environment,
		Args:        args,
	}
}

func (c *Client) killProcess() error {
	// Tear down the in-process FFI host on error paths that reuse killProcess to
	// abort a start (there is no OS process to kill in that mode).
	if c.ffiHost != nil {
		c.ffiHost.Dispose()
		c.ffiHost = nil
	}
	if p := c.osProcess.Swap(nil); p != nil {
		if err := p.Kill(); err != nil {
			return fmt.Errorf("failed to kill CLI process: %w", err)
		}
	}
	c.process = nil
	return nil
}

func (c *Client) killProcessAndWait() error {
	done := c.processDone
	killErr := c.killProcess()
	if done == nil {
		return killErr
	}

	select {
	case <-done:
		return killErr
	case <-time.After(processExitTimeout):
		return errors.Join(killErr, fmt.Errorf("timed out waiting for CLI process to exit after kill"))

View on GitHub (pinned to cd8cf15dc3)