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

  1. Retry the send against a newly created workspace after treating the old one as gone
  2. Serialize teardown and sends so SendMessage completes before DeleteWorkspace is invoked
  3. 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

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


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