{"record":{"id":"f871e2f752d9cfa4","repo":"github/copilot-sdk","slug":"failed-to-kill-cli-process-w","errorCode":null,"errorMessage":"failed to kill CLI process: %w","messagePattern":"failed to kill CLI process: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"go/client.go","lineNumber":2364,"sourceCode":"\t\tenvironment[\"COPILOT_DISABLE_KEYTAR\"] = \"1\"\n\t}\n\n\treturn inProcessHostConfig{\n\t\tEnvironment: environment,\n\t\tArgs:        args,\n\t}\n}\n\nfunc (c *Client) killProcess() error {\n\t// Tear down the in-process FFI host on error paths that reuse killProcess to\n\t// abort a start (there is no OS process to kill in that mode).\n\tif c.ffiHost != nil {\n\t\tc.ffiHost.Dispose()\n\t\tc.ffiHost = nil\n\t}\n\tif p := c.osProcess.Swap(nil); p != nil {\n\t\tif err := p.Kill(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to kill CLI process: %w\", err)\n\t\t}\n\t}\n\tc.process = nil\n\treturn nil\n}\n\nfunc (c *Client) killProcessAndWait() error {\n\tdone := c.processDone\n\tkillErr := c.killProcess()\n\tif done == nil {\n\t\treturn killErr\n\t}\n\n\tselect {\n\tcase <-done:\n\t\treturn killErr\n\tcase <-time.After(processExitTimeout):\n\t\treturn errors.Join(killErr, fmt.Errorf(\"timed out waiting for CLI process to exit after kill\"))","sourceCodeStart":2346,"sourceCodeEnd":2382,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/go/client.go#L2346-L2382","documentation":"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.","triggerScenarios":"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.","commonSituations":"Double-stop from concurrent callers, killing a process owned by another user or in an uninterruptible state, container environments restricting signals.","solutions":["Ignore 'process already finished' style errors — the process is gone, which is the goal; wrap Stop in a tolerated check.","Avoid concurrent Stop calls; serialize with your own mutex or use the client's forceStop path only once.","Check permissions in containers/restricted environments (signal rights, PID namespaces).","If the process is stuck in D state, inspect the OS (dmesg) rather than retrying the kill."],"exampleFix":"// before\nif err := client.Stop(ctx); err != nil { return err }\n// after\nif err := client.Stop(ctx); err != nil {\n    if !strings.Contains(err.Error(), \"process already finished\") {\n        return err\n    }\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := client.Stop(ctx); err != nil {\n    if strings.Contains(err.Error(), \"process already finished\") {\n        return nil // process is gone; stop goal achieved\n    }\n    return err\n}","preventionTips":["Serialize Stop calls; never stop the same client concurrently.","Treat 'already finished' kill errors as success.","Check signal permissions in containers before relying on graceful kill.","Monitor for stray CLI processes to detect repeated kill failures."],"tags":["process","kill","cleanup","signals"],"backgroundTag":"process-kill-failed","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}