multica-ai/multica · error
errMsg
Error message
errMsg
What it means
HTTP 401 from the realtime WebSocket upgrade handler when the auth cookie is present but authenticateToken rejects its value. authenticateToken validates the cookie's token (PAT or JWT) and returns a non-empty error message on failure — expired session, revoked token, wrong signing secret, or malformed token — and the handler passes that message straight into http.Error. Note this path does not run CSRF validation, unlike the HTTP middleware.
Source
Thrown at server/internal/realtime/hub.go:789
if slug := r.URL.Query().Get("workspace_slug"); slug != "" && resolveSlug != nil {
resolved, err := resolveSlug(r.Context(), slug)
if err != nil {
http.Error(w, `{"error":"workspace not found"}`, http.StatusNotFound)
return
}
workspaceID = resolved
}
}
if workspaceID == "" {
http.Error(w, `{"error":"workspace_id or workspace_slug required"}`, http.StatusBadRequest)
return
}
var userID string
if cookie, err := r.Cookie(auth.AuthCookieName); err == nil && cookie.Value != "" {
uid, errMsg := authenticateToken(cookie.Value, pr, r.Context())
if errMsg != "" {
http.Error(w, errMsg, http.StatusUnauthorized)
return
}
if !mc.IsMember(r.Context(), uid, workspaceID) {
http.Error(w, `{"error":"not a member of this workspace"}`, http.StatusForbidden)
return
}
userID = uid
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
slog.Error("websocket upgrade failed", "error", err)
return
}
// Bound inbound messages here rather than in readPump: the token auth
// path below reads its first frame before the caller is authenticated, so
// a limit installed any later leaves that read unbounded.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Re-authenticate in the browser (log in again) to get a fresh cookie, then reconnect the socket.
- If reconnect loops with 401, stop the loop and prompt re-login instead of hammering the endpoint.
- After rotating JWT_SECRET, expect all cookie sessions to invalidate — plan a re-login wave.
- Alternatively connect with a PAT via the token flow if the client supports it.
Example fix
// before: blind reconnect loop
socket.onclose = () => setTimeout(connect, 1000) // 401 loop
// after: stop and re-auth on 401
socket.onclose = (e) => {
if (e.code === 1008 || lastStatus === 401) { window.location = '/login'; return }
setTimeout(connect, 1000)
} Defensive patterns
Strategy: fallback
Validate before calling
// before connecting, cheap-check the session via REST
resp, _ := http.Get(base + "/api/me")
if resp.StatusCode == 401 { await relogin(); /* fresh cookie, then connect */ } Try / catch
conn, resp, err := dialer.Dial(wsURL, cookieHeader)
if err != nil && resp != nil && resp.StatusCode == 401 {
if ok := refreshSession(); !ok { promptLogin(); return }
conn, _, err = dialer.Dial(wsURL, cookieHeader) // one retry with fresh cookie
} Prevention
- Verify the session via a lightweight REST call before opening sockets.
- On 401, re-authenticate once and stop — never auto-loop reconnects with a dead cookie.
- Plan re-login flows after JWT_SECRET rotations; cookies die with the old secret.
When it happens
Trigger: Connecting to /ws with an auth cookie whose token is expired, revoked, signed with a different secret (server restart rotated JWT_SECRET), or corrupt; browser auto-sends a stale cookie after session expiry.
Common situations: Session expired while the tab stayed open and the socket reconnect logic keeps retrying; server secret rotation invalidating all cookies; cookie truncated by size limits; mixed environments (staging cookie sent to prod).
Related errors
- Invalid desktop runtime config JSON: ${err instanceof Error
- runtime_ids or user identity required
- missing authorization
- CSRF validation failed
- invalid token
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/43b28ca452da6031.
Report an issue: GitHub.