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 = nil

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Call GET /api/gateway/status to see whether the recorded PID is still alive
  2. 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
  3. 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
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/79f5c9cc86845782. Report an issue: GitHub.