{"record":{"id":"20c8cd0f8504d217","repo":"plandex-ai/plandex","slug":"failed-to-signal-litellm-for-shutdown-w","errorCode":null,"errorMessage":"failed to signal LiteLLM for shutdown: %w","messagePattern":"failed to signal LiteLLM for shutdown: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/server/model/litellm.go","lineNumber":67,"sourceCode":"\t\t\tcase <-ticker.C:\n\t\t\t\tif isLiteLLMHealthy() {\n\t\t\t\t\tlog.Println(\"LiteLLM proxy is healthy\")\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\tlog.Println(\"LiteLLM proxy is not healthy yet, retrying after 500ms...\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\treturn finalErr\n}\n\nfunc ShutdownLiteLLMServer() error {\n\tif liteLLMCmd != nil && liteLLMCmd.Process != nil {\n\t\tlog.Println(\"Shutting down LiteLLM proxy gracefully...\")\n\t\tif err := liteLLMCmd.Process.Signal(os.Interrupt); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to signal LiteLLM for shutdown: %w\", err)\n\t\t}\n\n\t\tdone := make(chan error, 1)\n\t\tgo func() {\n\t\t\tdone <- liteLLMCmd.Wait()\n\t\t}()\n\n\t\tselect {\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tlog.Println(\"LiteLLM proxy shutdown timed out, forcing kill\")\n\t\t\treturn liteLLMCmd.Process.Kill()\n\t\tcase err := <-done:\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/litellm.go#L49-L85","documentation":"During graceful shutdown, sending os.Interrupt to the LiteLLM proxy process failed. ShutdownLiteLLMServer returns this wrapped error immediately without waiting for the process to exit. Typically means the process handle is invalid or the process already died.","triggerScenarios":"liteLLMCmd.Process.Signal(os.Interrupt) returns an error: process already exited, process finished between the nil check and Signal, or OS-level permission problem signaling the child.","commonSituations":"Proxy crashed earlier (so Signal targets a zombie/dead PID); concurrent shutdown calls racing on liteLLMCmd; container runtime restricting signals; PID reuse edge cases.","solutions":["Ignore the error if the process already exited — check ProcessState before signalling","Guard against double shutdown with a sync.Once on ShutdownLiteLLMServer","Fall back to Process.Kill() if Interrupt fails and termination is required","Check earlier logs for a LiteLLM crash that would explain the dead process","In containers, ensure the signal is permitted (proper PID 1 handling)"],"exampleFix":"// before\nif err := liteLLMCmd.Process.Signal(os.Interrupt); err != nil {\n    return fmt.Errorf(\"failed to signal LiteLLM for shutdown: %w\", err)\n}\n// after: tolerate already-exited process\nif err := liteLLMCmd.Process.Signal(os.Interrupt); err != nil && liteLLMCmd.ProcessState == nil {\n    return fmt.Errorf(\"failed to signal LiteLLM for shutdown: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"if liteLLMCmd == nil || liteLLMCmd.Process == nil || liteLLMCmd.ProcessState != nil {\n    return nil // already shut down or never started\n}","typeGuard":"func processAlive(cmd *exec.Cmd) bool {\n    return cmd != nil && cmd.Process != nil && cmd.ProcessState == nil\n}","tryCatchPattern":"if err := ShutdownLiteLLMServer(); err != nil {\n    if processAlive(liteLLMCmd) {\n        _ = liteLLMCmd.Process.Kill()\n    }\n}","preventionTips":["Wrap shutdown in sync.Once to prevent double-signal races","Check ProcessState before signalling a possibly-dead process","Fall back to Kill when Interrupt fails","Log LiteLLM crashes so dead-process signals are expected, not surprising"],"tags":["litellm","shutdown","signal","process"],"backgroundTag":"process-signal-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}