{"record":{"id":"d0950f6c93214a28","repo":"charmbracelet/crush","slug":"server-is-shutting-down-d0950f","errorCode":null,"errorMessage":"server is shutting down","messagePattern":"server is shutting down","errorType":"exception","errorClass":"ErrServerShuttingDown","httpStatus":null,"severity":"error","filePath":"internal/client/errors.go","lineNumber":30,"sourceCode":"var (\n\t// ErrNotFound reports that the server answered 404. For a\n\t// workspace-scoped call this means the server no longer knows the\n\t// workspace — it was torn down, or the server was replaced under the\n\t// client — so the right response is to re-register rather than retry\n\t// the same ID, which can never start succeeding again.\n\tErrNotFound = errors.New(\"not found\")\n\n\t// ErrServerBusy reports that the server declined to shut down\n\t// because it is still hosting workspaces or is midway through\n\t// creating one. A client asking a version-mismatched server to stand\n\t// down must keep using it instead of assuming it is going away.\n\tErrServerBusy = errors.New(\"server busy\")\n\n\t// ErrServerShuttingDown reports that the server refused the request\n\t// because it has already committed to exiting. The work is not lost:\n\t// a replacement server can be started and the request retried\n\t// against it.\n\tErrServerShuttingDown = errors.New(\"server is shutting down\")\n\n\t// ErrUnsupported reports that the running server does not understand\n\t// the request because it predates the feature. Callers must decide\n\t// what is safe to do with an older server rather than treating the\n\t// failure as transient.\n\tErrUnsupported = errors.New(\"unsupported by the running server\")\n)\n\n// checkStatus returns nil when rsp's status code is one of ok\n// (http.StatusOK when none are given). Otherwise it returns an error\n// carrying the status code and, when the body decodes as a proto.Error,\n// the server-provided message. Statuses that callers act on are wrapped\n// in the matching sentinel. checkStatus may consume the response body.\nfunc checkStatus(rsp *http.Response, ok ...int) error {\n\tif len(ok) == 0 {\n\t\tok = []int{http.StatusOK}\n\t}\n\tif slices.Contains(ok, rsp.StatusCode) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/errors.go#L12-L48","documentation":"ErrServerShuttingDown (client package) reports that the server refused a request because it has already committed to exiting. checkStatus maps HTTP 503 responses to this sentinel, and admitLocked enforces it server-side. The work is not lost: a replacement server can be started and the request retried against it.","triggerScenarios":"Any client call whose response carries http.StatusServiceUnavailable (checkStatus); createWorkspaceOnLiveServer when the live server is tearing down; admitLocked rejecting a request after shutdown began; runSubscription hitting a 503 mid-stream setup.","commonSituations":"Reusing a server inside its idle shutdown window (DefaultIdleShutdownDelay); a client attaching exactly as the previous server exits; CI races where one step stops the server while another still talks to it.","solutions":["Start a replacement server and retry the request against it","Match with errors.Is(err, client.ErrServerShuttingDown) and never retry the same server","Raise CRUSH_SERVER_IDLE_TIMEOUT to widen the reuse window and avoid racing shutdown"],"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    if err := client.StartReplacementServer(ctx); err != nil { return err }\n    ws, err = client.CreateWorkspace(ctx, path)\n}\nreturn err","handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isErrServerShuttingDown(err error) bool {\n    return errors.Is(err, client.ErrServerShuttingDown)\n}","tryCatchPattern":"rsp, err := client.do(req)\nif err != nil {\n    if errors.Is(err, client.ErrServerShuttingDown) {\n        return retryOnFreshServer(ctx, req) // 503 -> replacement server\n    }\n    return err\n}","preventionTips":["Map HTTP 503 to 'start a replacement server', never to transient retry","Widen CRUSH_SERVER_IDLE_TIMEOUT to avoid attaching during teardown","Add shutdown barriers in CI so no step talks to a server another step stopped"],"tags":["shutdown","http-503","retry"],"backgroundTag":"server-shutting-down","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}