{"record":{"id":"90894ed18cde91db","repo":"sipeed/picoclaw","slug":"failed-to-stop-gateway-pid-d-v","errorCode":null,"errorMessage":"Failed to stop gateway (PID %d): %v","messagePattern":"Failed to stop gateway \\(PID (.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/gateway.go","lineNumber":1279,"sourceCode":"// Note: Unlike StopGateway (which only stops self-started processes), this API endpoint\n// stops any gateway process, including attached ones. This is intentional for user control.\n//\n//\tPOST /api/gateway/stop\nfunc (h *Handler) handleGatewayStop(w http.ResponseWriter, r *http.Request) {\n\tgateway.mu.Lock()\n\tdefer gateway.mu.Unlock()\n\n\tif gateway.cmd == nil || gateway.cmd.Process == nil {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\t\"status\": \"not_running\",\n\t\t})\n\t\treturn\n\t}\n\n\tpid, err := stopGatewayLocked()\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to stop gateway (PID %d): %v\", pid, err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\"status\": \"ok\",\n\t\t\"pid\":    pid,\n\t})\n}\n\n// RestartGateway restarts the gateway process. This is a non-blocking operation\n// that stops the current gateway (if running) and starts a new one.\n// Returns the PID of the new gateway process or an error.\nfunc (h *Handler) RestartGateway() (int, error) {\n\tready, reason, err := h.gatewayStartReady()\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to validate gateway start conditions: %w\", err)\n\t}","sourceCodeStart":1261,"sourceCodeEnd":1297,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/gateway.go#L1261-L1297","documentation":"Returned by POST /api/gateway/stop when stopGatewayLocked() fails (gateway.go:942-974). Two causes: the safety guard 'refuse to stop non-gateway process (PID %d)' - the tracked PID was recycled to a non-picoclaw process (gatewayProcessMatcher inspected it and it is not a gateway), so the backend deliberately refuses to signal it - or the SIGTERM (SIGKILL on Windows) itself failed, e.g. 'os: process already finished' for a process that exited between the liveness check and the signal, or permission denied.","triggerScenarios":"Gateway died, OS recycled its PID to an unrelated process, and the API still tracks the old PID; double-stop race where the process exits right after the guard check; signaling a zombie child that cannot receive SIGTERM.","commonSituations":"Long-running hosts where PID reuse is likely; external kill -9 of the gateway leaving stale tracked state; rapid stop/status/stop sequences from automation.","solutions":["Call GET /api/gateway/status - if the gateway is not running, there is nothing to stop","If the refuse-guard tripped, identify the PID with ps before touching it - it belongs to another program; do NOT kill it blindly","Use POST /api/gateway/restart instead, which re-validates and resets tracked state, or restart the web backend to clear stale tracking","For 'process already finished' races, simply re-check status - the stop effectively succeeded"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Only attempt a stop when the API itself reports a running gateway.\nconst st = await (await fetch('/api/gateway/status')).json();\nif (st.gateway_status !== 'running') {\n  return {skipped: true};   // nothing to stop - avoids the not_running/race paths\n}","typeGuard":null,"tryCatchPattern":"const res = await fetch('/api/gateway/stop', {method: 'POST'});\nconst body = await res.json().catch(() => null);\nif (res.ok || body?.status === 'not_running') return;      // success flavors\nif (res.status === 500) {\n  const text = await res.text();\n  if (text.includes('refuse to stop non-gateway process')) {\n    // safety guard: PID was recycled - do NOT retry, do NOT kill manually\n    throw new Error('Tracked PID belongs to another process; restart the launcher to reset state');\n  }\n  if (text.includes('already finished')) return;           // race: it exited anyway\n  throw new Error(text);\n}","preventionTips":["Never kill the gateway with OS signals from outside; always use the stop/restart endpoints","Check status before issuing stop","After any external process management incident, restart the web backend to clear stale PID tracking"],"tags":["gateway","process","signals","pid-reuse","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}