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 wrapped

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Keep using the existing server — the refusal means it still has live work
  2. Release all workspaces before retrying the shutdown
  3. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/5cf4b2a28da22efd. Report an issue: GitHub.