{"record":{"id":"b36810b722f59e87","repo":"charmbracelet/crush","slug":"server-shutdown-failed-s","errorCode":null,"errorMessage":"server shutdown failed: %s","messagePattern":"server shutdown failed: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/client.go","lineNumber":139,"sourceCode":"// variant: a client only ever wants a server replaced, never other\n// sessions killed.\n//\n// A server that declines because it is in use returns an error wrapping\n// [ErrServerBusy]. A server too old to know the command returns\n// [ErrUnsupported]; it must be left running, since the shutdown request\n// it does understand is unconditional and would take its sessions down.\nfunc (c *Client) ShutdownServerIfIdle(ctx context.Context) error {\n\trsp, err := c.post(ctx, \"/control\", nil, jsonBody(proto.ServerControl{\n\t\tCommand: proto.ServerControlShutdownIfIdle,\n\t}), nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode == http.StatusOK {\n\t\treturn nil\n\t}\n\tfailure := fmt.Errorf(\"server shutdown failed: %s\", rsp.Status)\n\tswitch rsp.StatusCode {\n\tcase http.StatusConflict:\n\t\treturn fmt.Errorf(\"%w: %w\", ErrServerBusy, failure)\n\tcase http.StatusBadRequest:\n\t\t// The only way a well-formed control request is rejected as bad\n\t\t// is an unknown command, i.e. a server predating this one.\n\t\treturn fmt.Errorf(\"%w: %w\", ErrUnsupported, failure)\n\t}\n\treturn failure\n}\n\n// ShutdownServer sends the original, unconditional \"shutdown\" command.\n// It exists for backward compatibility with servers that predate\n// [ServerControlShutdownIfIdle]: those servers reject the idle-checked\n// variant with [ErrUnsupported], so a client that has already verified\n// the server is idle (e.g. via [Client.ListWorkspaces]) can fall back to\n// this command to replace an old server.\n//","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/client.go#L121-L157","documentation":"ShutdownServerIfIdle sends an idle-shutdown control command; this base error is created when the response status is not 200 OK. It is the plain failure case for any unexpected status (e.g. 500), while 409 and 400 are further wrapped with ErrServerBusy and ErrUnsupported sentinel errors. Note this error is returned unwrapped in the default switch branch, so callers should errors.Is-check sentinels, not string-match.","triggerScenarios":"Calling ShutdownServerIfIdle and receiving any non-200 status other than 409/400 — most commonly a 500 from the server failing to persist state before shutdown, or a 502/503 from a proxy in front of the server.","commonSituations":"Server bug during shutdown (DB close failure) yielding 500; infrastructure proxy intercepting the control endpoint; server version drift where a new status code appears.","solutions":["Check the HTTP status embedded in the message to classify the failure.","For 500s, inspect server logs for the shutdown-time failure (DB close, flush errors).","If behind a proxy, hit the server address directly.","If the server is busy the caller should instead see ErrServerBusy (409) — handle that sentinel separately and retry later."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if err := client.Health(ctx); err != nil {\n    return fmt.Errorf(\"server unhealthy before shutdown attempt: %w\", err)\n}","typeGuard":"func IsPlainShutdownFailure(err error) bool {\n    return err != nil && !errors.Is(err, ErrServerBusy) && !errors.Is(err, ErrUnsupported) &&\n        strings.Contains(err.Error(), \"server shutdown failed\")\n}","tryCatchPattern":"if err := client.ShutdownServerIfIdle(ctx); err != nil {\n    switch {\n    case errors.Is(err, ErrServerBusy):\n        return nil\n    case errors.Is(err, ErrUnsupported):\n        return client.ShutdownServer(ctx)\n    default:\n        return fmt.Errorf(\"unexpected shutdown status: %w\", err)\n    }\n}","preventionTips":["Always branch on errors.Is for ErrServerBusy/ErrUnsupported before generic handling.","Check server logs for 500s raised during state persistence at shutdown.","Keep client and server versions aligned.","Probe /health before issuing control commands."],"tags":["http","shutdown","client","lifecycle"],"backgroundTag":"server-shutdown-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}