sipeed/picoclaw · error
Failed to attach to gateway: %v
Error message
Failed to attach to gateway: %v
What it means
Returned by POST /api/gateway/start when a sanitized PID file indicates a running gateway, and h.startGatewayLocked("starting", pid) fails to attach to that PID. The attach path (gateway.go:1008-1028) can fail in config.LoadConfig or in attachToGatewayProcessLocked -> os.FindProcess (gateway.go:854), which fails for an inaccessible/invalid PID on Windows and practically only for races on POSIX. The most common real cause is a race: the gateway process died between the PID-file liveness check and the attach attempt. The backend log line 'Failed to attach to running gateway (PID: N)' carries the wrapped reason.
Source
Thrown at web/backend/api/gateway.go:1206
http.StatusInternalServerError,
)
return
}
if !ready {
gateway.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]any{
"status": "precondition_failed",
"message": reason,
})
return
}
_, err = h.startGatewayLocked("starting", pid)
if err != nil {
gateway.mu.Unlock()
logger.ErrorC("gateway", fmt.Sprintf("Failed to attach to running gateway (PID: %d): %v", pid, err))
http.Error(w, fmt.Sprintf("Failed to attach to gateway: %v", err), http.StatusInternalServerError)
return
}
gateway.pidData = pidData
gateway.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
"pid": pid,
})
return
}
gateway.mu.Lock()
defer gateway.mu.Unlock()
if gateway.cmd != nil && gateway.cmd.Process != nil {
gateway.cmd = nilView on GitHub (pinned to 49183d7e8d)
Solutions
- Call GET /api/gateway/status to see whether the recorded PID is still alive
- Retry POST /api/gateway/start - if the old process truly died, the stale PID file is detected and cleaned, and a fresh gateway starts via the normal path
- If it keeps failing, check backend logs for the wrapped error, then remove the stale gateway PID file in the global config dir and start again
- On Windows, confirm the backend has permission to open the recorded process handle
Defensive patterns
Strategy: retry
Validate before calling
// Confirm the PID-file gateway is really alive before asking to attach.
const st = await (await fetch('/api/gateway/status')).json();
if (st.gateway_status !== 'running') {
// stale PID file will be cleaned on start; safe to proceed without attach
} Try / catch
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch('/api/gateway/start', {method: 'POST'});
if (res.ok) return res.json();
const text = await res.text();
if (res.status === 500 && text.startsWith('Failed to attach')) {
await sleep(1500); // let the dead-PID race settle
continue; // retry: stale PID file gets cleaned, fresh start succeeds
}
throw new Error(text);
} Prevention
- Do not pkill the gateway and immediately call start - leave a second for PID-file cleanup
- Prefer POST /api/gateway/stop over external kills so tracked state stays consistent
- Poll /api/gateway/status after kills to confirm the process is gone before starting
When it happens
Trigger: The gateway crashes or is killed exactly while the start request is in flight, leaving a PID file whose process vanished; on Windows, OpenProcess access-denied for the recorded PID; a stale but momentarily-alive-looking PID file after an unclean shutdown.
Common situations: Scripts that pkill the gateway and immediately call the start endpoint; OOM killer reaping the gateway during a start call; PID-file left behind after a host crash while the launcher restarts.
Related errors
- Failed to start gateway: %v
- Failed to stop gateway (PID %d): %v
- Failed to restart gateway: %v
- session not found
- session already completed
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/79f5c9cc86845782.
Report an issue: GitHub.