{"record":{"id":"3ed706e3890fd515","repo":"charmbracelet/crush","slug":"client-not-attached","errorCode":null,"errorMessage":"client not attached","messagePattern":"client not attached","errorType":"exception","errorClass":"ErrClientNotAttached","httpStatus":null,"severity":"error","filePath":"internal/backend/backend.go","lineNumber":38,"sourceCode":"\t\"github.com/charmbracelet/crush/internal/csync\"\n\t\"github.com/charmbracelet/crush/internal/db\"\n\t\"github.com/charmbracelet/crush/internal/proto\"\n\t\"github.com/charmbracelet/crush/internal/skills\"\n\t\"github.com/charmbracelet/crush/internal/ui/util\"\n\t\"github.com/charmbracelet/crush/internal/version\"\n\t\"github.com/google/uuid\"\n)\n\n// Common errors returned by backend operations.\nvar (\n\tErrWorkspaceNotFound       = errors.New(\"workspace not found\")\n\tErrLSPClientNotFound       = errors.New(\"LSP client not found\")\n\tErrAgentNotInitialized     = errors.New(\"agent coordinator not initialized\")\n\tErrPathRequired            = errors.New(\"path is required\")\n\tErrInvalidPermissionAction = errors.New(\"invalid permission action\")\n\tErrUnknownCommand          = errors.New(\"unknown command\")\n\tErrInvalidClientID         = errors.New(\"invalid client_id\")\n\tErrClientNotAttached       = errors.New(\"client not attached\")\n\tErrWorkspaceClosing        = errors.New(\"workspace closing\")\n\tErrServerShuttingDown      = errors.New(\"server is shutting down\")\n\tErrServerNotIdle           = errors.New(\"server is hosting live workspaces\")\n\tErrClientRetired           = errors.New(\"client has been retired\")\n\tErrChannelOptInMismatch    = errors.New(\"requested channels differ from the existing workspace; channels are an explicit opt-in and are not shared across duplicate creates\")\n)\n\n// DefaultCreateGrace is the window in which a client must open an SSE\n// stream after creating a workspace before its creation hold is\n// released. Exposed as a package variable so tests can shorten it.\nvar DefaultCreateGrace = 30 * time.Second\n\n// DefaultIdleShutdownDelay is how long the server stays alive after its\n// last workspace is released before it shuts itself down. The delay\n// exists so a client that closes one session and opens another moments\n// later (the same directory or a different one) reuses the still-running\n// server instead of racing its shutdown: with an immediate shutdown the\n// new client can attach to — or create a workspace on — a server that is","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/backend/backend.go#L20-L56","documentation":"ErrClientNotAttached reports that an operation requiring the caller's client to hold an active attachment (SSE stream claim) on a workspace was attempted without one. The backend treats the attachment as a refcount on the workspace; without it the client has no standing to issue workspace-scoped calls. Returned by SetCurrentSession and surfaced through handleError.","triggerScenarios":"Calling SetCurrentSession (or other workspace-scoped operations) from a client that never attached, whose attachment was released, or whose SSE stream dropped and was not re-established before the detach grace elapsed.","commonSituations":"Reusing a workspace ID after a client restart without re-attaching; a dropped network connection severing the SSE stream; calling session APIs before completing the create/attach handshake.","solutions":["Attach (open the SSE stream) before issuing workspace-scoped calls","On reconnect, re-attach to the workspace and re-issue the operation","Verify the client did not explicitly release the workspace earlier in its lifecycle"],"exampleFix":"// before\nbackend.SetCurrentSession(ctx, clientID, sessionID) // never attached\n// after\nws, err := backend.AttachClient(ctx, clientID, path)\nif err != nil { return err }\nbackend.SetCurrentSession(ctx, clientID, sessionID)","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isErrClientNotAttached(err error) bool {\n    return errors.Is(err, backend.ErrClientNotAttached)\n}","tryCatchPattern":"err := backend.SetCurrentSession(ctx, clientID, sessionID)\nswitch {\ncase errors.Is(err, backend.ErrClientNotAttached):\n    if _, aerr := backend.AttachClient(ctx, clientID, path); aerr != nil {\n        return aerr\n    }\n    return backend.SetCurrentSession(ctx, clientID, sessionID)\ncase err != nil:\n    return err\n}","preventionTips":["Open the SSE attachment before any workspace-scoped call","Re-attach immediately on stream drop, inside the detach grace window","Centralize attach state in one client object so calls cannot skip it"],"tags":["backend","lifecycle","workspace"],"backgroundTag":"client-not-attached","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}