multica-ai/multica · error

workspace not found

Error message

workspace not found

What it means

HTTP 404 from the realtime WebSocket upgrade handler when no workspace_id query param was supplied, a workspace_slug was given instead, and the slug resolver (e.g. a DB lookup mapping slug → workspace UUID) returned an error. The slug does not resolve to any workspace: it is unknown, mistyped, or the lookup itself failed; the handler collapses both to a single 404 'workspace not found'.

Source

Thrown at server/internal/realtime/hub.go:774

		return false
	}
	return true
}

func writeWSAuthErrorAndClose(conn *websocket.Conn, payload []byte, attrs ...any) {
	writeWSAuthFrame(conn, payload, "auth_error", attrs...)
	conn.Close()
}

// HandleWebSocket upgrades an HTTP connection to WebSocket with cookie or
// first-message auth.
func HandleWebSocket(hub *Hub, mc MembershipChecker, pr PATResolver, resolveSlug SlugResolver, w http.ResponseWriter, r *http.Request) {
	workspaceID := r.URL.Query().Get("workspace_id")
	if workspaceID == "" {
		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) {

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Verify the slug against the workspace list API (GET /api/workspaces) and correct it.
  2. Prefer workspace_id (the UUID) when known — it skips slug resolution entirely.
  3. If the workspace was renamed, refresh the page/client so it picks up the new slug or the ID.
  4. If the resolver logs an error, check DB connectivity/permissions on the server.

Example fix

// before: connect by a possibly stale slug
new WebSocket("ws://host/ws?workspace_slug=" + slug)

// after: resolve the id once and connect by id
const ws = await api.workspaces.list()
const id = ws.find(w => w.slug === slug)?.id
if (id) new WebSocket("ws://host/ws?workspace_id=" + id)
Defensive patterns

Strategy: validation

Validate before calling

// resolve slug → id via the REST API before opening the socket
wsList, err := api.ListWorkspaces(ctx)
if err != nil { return err }
var id string
for _, w := range wsList { if w.Slug == slug { id = w.ID; break } }
if id == "" { return fmt.Errorf("unknown workspace slug: %s", slug) }

Type guard

func hasWorkspaceTarget(idParam, slug string) bool { return idParam != "" || slug != "" }

Try / catch

conn, resp, err := dialer.Dial(url, nil)
if err != nil && resp != nil && resp.StatusCode == 404 {
    // slug stale: re-resolve via REST, then reconnect with workspace_id once
}

Prevention

When it happens

Trigger: GET /ws?workspace_slug=does-not-exist (typo, deleted workspace, wrong organization scope); slug resolver DB error; slug containing URL-unsafe characters that mangle the lookup.

Common situations: Workspace renamed or deleted between page load and socket connect; user pastes an old invite link with a stale slug; per-workspace DB routing looking in the wrong shard.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/581a59fca0492901. Report an issue: GitHub.