charmbracelet/crush · error · ErrWorkspaceClosing
workspace closing
Error message
workspace closing
What it means
ErrWorkspaceClosing reports that a message was submitted to a workspace that is in the process of being torn down. The backend refuses new work once it has committed to closing, because the agent coordinator will not be available to process it. Returned by SendMessage and mapped through handleError.
Source
Thrown at internal/backend/backend.go:39
"github.com/charmbracelet/crush/internal/db"
"github.com/charmbracelet/crush/internal/proto"
"github.com/charmbracelet/crush/internal/skills"
"github.com/charmbracelet/crush/internal/ui/util"
"github.com/charmbracelet/crush/internal/version"
"github.com/google/uuid"
)
// Common errors returned by backend operations.
var (
ErrWorkspaceNotFound = errors.New("workspace not found")
ErrLSPClientNotFound = errors.New("LSP client not found")
ErrAgentNotInitialized = errors.New("agent coordinator not initialized")
ErrPathRequired = errors.New("path is required")
ErrInvalidPermissionAction = errors.New("invalid permission action")
ErrUnknownCommand = errors.New("unknown command")
ErrInvalidClientID = errors.New("invalid client_id")
ErrClientNotAttached = errors.New("client not attached")
ErrWorkspaceClosing = errors.New("workspace closing")
ErrServerShuttingDown = errors.New("server is shutting down")
ErrServerNotIdle = errors.New("server is hosting live workspaces")
ErrClientRetired = errors.New("client has been retired")
ErrChannelOptInMismatch = errors.New("requested channels differ from the existing workspace; channels are an explicit opt-in and are not shared across duplicate creates")
)
// DefaultCreateGrace is the window in which a client must open an SSE
// stream after creating a workspace before its creation hold is
// released. Exposed as a package variable so tests can shorten it.
var DefaultCreateGrace = 30 * time.Second
// DefaultIdleShutdownDelay is how long the server stays alive after its
// last workspace is released before it shuts itself down. The delay
// exists so a client that closes one session and opens another moments
// later (the same directory or a different one) reuses the still-running
// server instead of racing its shutdown: with an immediate shutdown the
// new client can attach to — or create a workspace on — a server that is
// already tearing down, and then observe its coder agent as "offline".View on GitHub (pinned to 7944b8e522)
Solutions
- Retry the send against a newly created workspace after treating the old one as gone
- Serialize teardown and sends so SendMessage completes before DeleteWorkspace is invoked
- Keep the attachment (SSE stream) open for the duration of message traffic
Example fix
// before
if err := backend.SendMessage(ctx, clientID, msg); err != nil { return err }
// after
if err := backend.SendMessage(ctx, clientID, msg); err != nil {
if errors.Is(err, backend.ErrWorkspaceClosing) {
ws, err := backend.CreateWorkspace(ctx, clientID, path)
if err != nil { return err }
return backend.SendMessage(ctx, clientID, msg)
}
return err
} Defensive patterns
Strategy: type-guard
Type guard
func isErrWorkspaceClosing(err error) bool {
return errors.Is(err, backend.ErrWorkspaceClosing)
} Try / catch
if err := backend.SendMessage(ctx, clientID, msg); err != nil {
if errors.Is(err, backend.ErrWorkspaceClosing) {
return recreateWorkspaceAndResend(ctx, clientID, path, msg)
}
return err
} Prevention
- Drain in-flight SendMessage calls before invoking DeleteWorkspace
- Keep the SSE attachment open until all message traffic completes
- Avoid background goroutines sending messages after teardown begins
When it happens
Trigger: Calling SendMessage on a workspace whose teardown has begun — the client's claim was released, detach grace expired, or an explicit DeleteWorkspace raced an in-flight send.
Common situations: Racing a clean exit: a background goroutine sends a final message while the main path tears the workspace down; a proxy timeout drops the SSE stream and the workspace closes before reconnect.
Related errors
- client not attached
- server is shutting down
- server is hosting live workspaces
- client has been retired
- requested channels differ from the existing workspace; chann
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/b04f71e44986a8cc.
Report an issue: GitHub.