{"record":{"id":"1d9456596d2f9c67","repo":"charmbracelet/crush","slug":"errunsupported","errorCode":"ErrUnsupported","errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"error_code","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"internal/client/client.go","lineNumber":146,"sourceCode":"func (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//\n// New servers apply the same idleness check to this command as they do\n// to [ServerControlShutdownIfIdle], so it is never more dangerous.\nfunc (c *Client) ShutdownServer(ctx context.Context) error {\n\trsp, err := c.post(ctx, \"/control\", nil, jsonBody(proto.ServerControl{\n\t\tCommand: proto.ServerControlShutdown,\n\t}), nil)\n\tif err != nil {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/client.go#L128-L164","documentation":"When ShutdownServerIfIdle receives HTTP 400 Bad Request, the server rejected the command as unknown — meaning the running server predates the idle-shutdown command (version drift). The client wraps ErrUnsupported with the base failure so errors.Is(err, ErrUnsupported) identifies legacy servers, letting callers fall back to ShutdownServer.","triggerScenarios":"Calling ShutdownServerIfIdle against an older crush server binary that only knows the legacy \"shutdown\" command, so its router answers 400 Bad Request for the new command.","commonSituations":"Upgraded client with a still-running old server process from a previous version; PATH pointing at an old binary while an old daemon lingers; mixed-version client/server setups.","solutions":["Detect with errors.Is(err, ErrUnsupported) and fall back to the legacy ShutdownServer call.","Kill the stale old server process and start the upgraded binary.","Align client and server versions (restart the daemon after upgrading).","Pin the server binary path in launchers so both sides upgrade together."],"exampleFix":"// before\nerr := client.ShutdownServerIfIdle(ctx)\nif err != nil { return err }\n// after\nif err := client.ShutdownServerIfIdle(ctx); err != nil {\n    if errors.Is(err, ErrUnsupported) {\n        return client.ShutdownServer(ctx) // legacy fallback\n    }\n    return err\n}","handlingStrategy":"fallback","validationCode":"// probe whether the server supports the idle-shutdown command\nif !client.SupportsIdleShutdown(ctx) {\n    return client.ShutdownServer(ctx)\n}","typeGuard":"func IsUnsupported(err error) bool {\n    return errors.Is(err, ErrUnsupported)\n}","tryCatchPattern":"if err := client.ShutdownServerIfIdle(ctx); err != nil {\n    if errors.Is(err, ErrUnsupported) {\n        return client.ShutdownServer(ctx) // legacy server fallback\n    }\n    return err\n}","preventionTips":["Restart the daemon after upgrading the client so versions match.","Keep a legacy shutdown fallback path in all shutdown logic.","Version-check the running server at client startup.","Kill stale old-version server processes before starting new ones."],"tags":["http","version-compatibility","shutdown","sentinel-error"],"backgroundTag":"unsupported-command-version-drift","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}