sipeed/picoclaw · error
Failed to stop gateway (PID %d): %v
Error message
Failed to stop gateway (PID %d): %v
What it means
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.
Source
Thrown at web/backend/api/gateway.go:1279
// Note: Unlike StopGateway (which only stops self-started processes), this API endpoint
// stops any gateway process, including attached ones. This is intentional for user control.
//
// POST /api/gateway/stop
func (h *Handler) handleGatewayStop(w http.ResponseWriter, r *http.Request) {
gateway.mu.Lock()
defer gateway.mu.Unlock()
if gateway.cmd == nil || gateway.cmd.Process == nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"status": "not_running",
})
return
}
pid, err := stopGatewayLocked()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to stop gateway (PID %d): %v", pid, err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
"pid": pid,
})
}
// RestartGateway restarts the gateway process. This is a non-blocking operation
// that stops the current gateway (if running) and starts a new one.
// Returns the PID of the new gateway process or an error.
func (h *Handler) RestartGateway() (int, error) {
ready, reason, err := h.gatewayStartReady()
if err != nil {
return 0, fmt.Errorf("failed to validate gateway start conditions: %w", err)
}View on GitHub (pinned to 49183d7e8d)
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
Defensive patterns
Strategy: validation
Validate before calling
// Only attempt a stop when the API itself reports a running gateway.
const st = await (await fetch('/api/gateway/status')).json();
if (st.gateway_status !== 'running') {
return {skipped: true}; // nothing to stop - avoids the not_running/race paths
} Try / catch
const res = await fetch('/api/gateway/stop', {method: 'POST'});
const body = await res.json().catch(() => null);
if (res.ok || body?.status === 'not_running') return; // success flavors
if (res.status === 500) {
const text = await res.text();
if (text.includes('refuse to stop non-gateway process')) {
// safety guard: PID was recycled - do NOT retry, do NOT kill manually
throw new Error('Tracked PID belongs to another process; restart the launcher to reset state');
}
if (text.includes('already finished')) return; // race: it exited anyway
throw new Error(text);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to restart gateway: %v
- Failed to attach to gateway: %v
- Failed to start gateway: %v
- session not found
- session already completed
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/90894ed18cde91db.
Report an issue: GitHub.