{"record":{"id":"9c434edf89f7107a","repo":"charmbracelet/crush","slug":"server-is-shutting-down","errorCode":null,"errorMessage":"server is shutting down","messagePattern":"server is shutting down","errorType":"exception","errorClass":"ErrServerShuttingDown","httpStatus":null,"severity":"error","filePath":"internal/backend/backend.go","lineNumber":40,"sourceCode":"\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\n// already tearing down, and then observe its coder agent as \"offline\".\n// Any workspace create within the window cancels the pending shutdown.","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/backend/backend.go#L22-L58","documentation":"ErrServerShuttingDown reports that the server has committed to exiting and refuses new work: workspace creates during the shutdown path are rejected by admitLocked, and checkStatus maps HTTP 503 responses to this sentinel. The work is not lost — a replacement server can be started and the request retried against it. Declared in both internal/backend (server side) and internal/client (client-side sentinel for 503).","triggerScenarios":"CreateWorkspace or any request admitted via admitLocked after shutdown began; createWorkspaceOnLiveServer hitting a server that answered 503; checkStatus receiving http.StatusServiceUnavailable; handleShutdown racing a create.","commonSituations":"Client reusing a still-running server during its idle shutdown window; a new client starting at the same moment the old server tears down after its last workspace was released; tests shortening DefaultCreateGrace exposing the race.","solutions":["Start a fresh server and retry the workspace create against it","Match with errors.Is(err, client.ErrServerShuttingDown) and treat it as non-transient per server","Increase CRUSH_SERVER_IDLE_TIMEOUT so the server stays up through the create window"],"exampleFix":"// before\nws, err := client.CreateWorkspace(ctx, path)\nif err != nil { return err }\n// after\nws, err := client.CreateWorkspace(ctx, path)\nif errors.Is(err, client.ErrServerShuttingDown) {\n    return restartServerAndCreate(ctx, path)\n}\nif err != nil { return err }","handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isErrServerShuttingDown(err error) bool {\n    return errors.Is(err, backend.ErrServerShuttingDown) || errors.Is(err, client.ErrServerShuttingDown)\n}","tryCatchPattern":"ws, err := client.CreateWorkspace(ctx, path)\nif errors.Is(err, client.ErrServerShuttingDown) {\n    return startNewServerAndCreate(ctx, path) // fallback path\n}\nreturn err","preventionTips":["Tune CRUSH_SERVER_IDLE_TIMEOUT so reuse windows cover client startup","Never retry the same server after a 503 — it has committed to exiting","Serialize server shutdowns with workspace creates in automation scripts"],"tags":["shutdown","lifecycle","http-503"],"backgroundTag":"server-shutting-down","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}