{"record":{"id":"cdb4c8639cf6f0d3","repo":"charmbracelet/crush","slug":"server-is-hosting-live-workspaces","errorCode":null,"errorMessage":"server is hosting live workspaces","messagePattern":"server is hosting live workspaces","errorType":"exception","errorClass":"ErrServerNotIdle","httpStatus":null,"severity":"warning","filePath":"internal/backend/backend.go","lineNumber":41,"sourceCode":"\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\n// already tearing down, and then observe its coder agent as \"offline\".\n// Any workspace create within the window cancels the pending shutdown.\n// Overridable via CRUSH_SERVER_IDLE_TIMEOUT (seconds; 0 restores the","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/backend/backend.go#L23-L59","documentation":"ErrServerNotIdle reports that a shutdown request was refused because the server is still hosting live workspaces (or is midway through creating one). Shutdown is only permitted when the server has nothing in flight; the backend returns this from handlePostControl to protect running work. The client-side equivalent is ErrServerBusy.","triggerScenarios":"Posting a control shutdown request (handlePostControl) while at least one live workspace exists or a workspace create is in its grace window; calling ShutdownServerIfIdle on a server with active clients.","commonSituations":"A version-mismatched client asking the server to stand down while other clients still use it; automation scripts stopping the shared server while an editor session is open; create grace (DefaultCreateGrace) still ticking for a workspace the caller forgot about.","solutions":["Release/teardown all live workspaces first, then retry the shutdown","Treat the refusal as 'keep using the server' — continue working against it instead of forcing exit","Wait out DefaultCreateGrace (30s) or the in-flight create before retrying"],"exampleFix":"// before\nif err := client.ShutdownServerIfIdle(ctx); err != nil { return err }\n// after\nif err := client.ShutdownServerIfIdle(ctx); err != nil {\n    if errors.Is(err, client.ErrServerBusy) {\n        return nil // keep using the server; it still has live workspaces\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isErrServerNotIdle(err error) bool {\n    return errors.Is(err, backend.ErrServerNotIdle)\n}","tryCatchPattern":"for {\n    err := client.ShutdownServerIfIdle(ctx)\n    if err == nil || !errors.Is(err, client.ErrServerBusy) {\n        return err\n    }\n    select {\n    case <-ctx.Done():\n        return ctx.Err()\n    case <-time.After(time.Second):\n    }\n}","preventionTips":["Release every workspace before requesting shutdown","Wait out the create-grace window (30s) when a create was recently issued","Coordinate shutdown across all clients sharing the server"],"tags":["shutdown","lifecycle","concurrency"],"backgroundTag":"server-not-idle","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}