charmbracelet/crush · warning · ErrServerBusy
server busy
Error message
server busy
What it means
ErrServerBusy reports that the server declined a shutdown request because it is still hosting workspaces or is midway through creating one. It corresponds to backend.ErrServerNotIdle on the wire. A client asking a version-mismatched server to stand down must keep using it instead of assuming it is going away.
Source
Thrown at internal/client/errors.go:24
"net/http"
"slices"
)
// Typed outcomes callers need to distinguish from ordinary transport
// failures. Match them with errors.Is.
var (
// ErrNotFound reports that the server answered 404. For a
// workspace-scoped call this means the server no longer knows the
// workspace — it was torn down, or the server was replaced under the
// client — so the right response is to re-register rather than retry
// the same ID, which can never start succeeding again.
ErrNotFound = errors.New("not found")
// ErrServerBusy reports that the server declined to shut down
// because it is still hosting workspaces or is midway through
// creating one. A client asking a version-mismatched server to stand
// down must keep using it instead of assuming it is going away.
ErrServerBusy = errors.New("server busy")
// ErrServerShuttingDown reports that the server refused the request
// because it has already committed to exiting. The work is not lost:
// a replacement server can be started and the request retried
// against it.
ErrServerShuttingDown = errors.New("server is shutting down")
// ErrUnsupported reports that the running server does not understand
// the request because it predates the feature. Callers must decide
// what is safe to do with an older server rather than treating the
// failure as transient.
ErrUnsupported = errors.New("unsupported by the running server")
)
// checkStatus returns nil when rsp's status code is one of ok
// (http.StatusOK when none are given). Otherwise it returns an error
// carrying the status code and, when the body decodes as a proto.Error,
// the server-provided message. Statuses that callers act on are wrappedView on GitHub (pinned to 7944b8e522)
Solutions
- Keep using the existing server — the refusal means it still has live work
- Release all workspaces before retrying the shutdown
- Only force-terminate if you control every attached client and can release them first
Example fix
// before
err := client.ShutdownServer(ctx)
if err != nil { return err }
// after
err := client.ShutdownServer(ctx)
if errors.Is(err, client.ErrServerBusy) {
return nil // server still hosting live workspaces; keep using it
}
return err Defensive patterns
Strategy: type-guard
Type guard
func isErrServerBusy(err error) bool {
return errors.Is(err, client.ErrServerBusy)
} Try / catch
err := client.ShutdownServerIfIdle(ctx)
if errors.Is(err, client.ErrServerBusy) {
return nil // server still in use; continue working against it
}
return err Prevention
- Treat ErrServerBusy as 'do not shut down', not as a failure to escalate
- Verify no other clients are attached before requesting shutdown
- In version-mismatch flows, prefer using the running server over replacing it
When it happens
Trigger: ShutdownServer or ShutdownServerIfIdle receiving a refusal from a server with live workspaces or an in-flight create; TestServer_RefusesShutdownWhileWorkspaceLive exercises exactly this condition.
Common situations: Version-mismatch handling that tries to shut a newer server down while other clients are attached; automation stopping a shared server during active sessions; create-grace window still open for a recently created workspace.
Related errors
- server is shutting down
- server is hosting live workspaces
- server shutdown failed: %s
- client not attached
- workspace closing
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/5cf4b2a28da22efd.
Report an issue: GitHub.