charmbracelet/crush · error

failed to list workspaces: status code %d

Error message

failed to list workspaces: status code %d

What it means

ListWorkspaces expects HTTP 200 from GET /workspaces; any other status yields this error carrying the numeric code. Unlike checkStatus-based endpoints, this path bypasses sentinel wrapping, so callers only get the status number.

Source

Thrown at internal/client/proto.go:31

	"net/url"
	"time"

	"github.com/charmbracelet/crush/internal/config"
	"github.com/charmbracelet/crush/internal/message"
	"github.com/charmbracelet/crush/internal/proto"
	"github.com/charmbracelet/crush/internal/pubsub"
	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
)

// ListWorkspaces retrieves all workspaces from the server.
func (c *Client) ListWorkspaces(ctx context.Context) ([]proto.Workspace, error) {
	rsp, err := c.get(ctx, "/workspaces", nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to list workspaces: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list workspaces: status code %d", rsp.StatusCode)
	}
	var workspaces []proto.Workspace
	if err := json.NewDecoder(rsp.Body).Decode(&workspaces); err != nil {
		return nil, fmt.Errorf("failed to decode workspaces: %w", err)
	}
	return workspaces, nil
}

// CreateWorkspace creates a new workspace on the server.
func (c *Client) CreateWorkspace(ctx context.Context, ws proto.Workspace) (*proto.Workspace, error) {
	ws.ClientID = c.clientID
	rsp, err := c.post(ctx, "/workspaces", nil, jsonBody(ws), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return nil, fmt.Errorf("failed to create workspace: %w", err)
	}
	defer rsp.Body.Close()
	if err := checkStatus(rsp); err != nil {
		return nil, fmt.Errorf("failed to create workspace: %w", err)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check the status code: 404 implies an old/incompatible server — restart it with the current version.
  2. 503 implies the server is shutting down — start a fresh daemon.
  3. Verify the server address targets the intended daemon, not a stale process.
  4. Compare client and server versions to ensure the /workspaces endpoint exists.

Example fix

// before
workspaces, err := client.ListWorkspaces(ctx)
if err != nil {
	return err
}
// after
workspaces, err := client.ListWorkspaces(ctx)
if err != nil {
	var statusErr interface{ HTTPStatusCode() int }
	if errors.As(err, &statusErr) && statusErr.HTTPStatusCode() == http.StatusNotFound {
		return restartServerWithCurrentVersion(ctx)
	}
	return err
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify server compatibility before listing
resp, err := http.Get(baseURL + "/workspaces")
if err == nil && resp.StatusCode == http.StatusNotFound {
	return errors.New("server does not expose /workspaces; upgrade or restart the daemon")
}

Type guard

func statusCodeFromListError(err error) int {
	s := err.Error()
	var code int
	if n, _ := fmt.Sscanf(s, "failed to list workspaces: status code %d", &code); n == 1 {
		return code
	}
	return 0
}

Try / catch

workspaces, err := client.ListWorkspaces(ctx)
if err != nil {
	switch statusCodeFromListError(err) {
	case http.StatusNotFound:
		return restartServerWithCurrentVersion(ctx)
	case http.StatusServiceUnavailable:
		return startFreshServer(ctx)
	default:
		return err
	}
}

Prevention

When it happens

Trigger: Calling ListWorkspaces against a server that responds non-200 — e.g. 404 because the server version lacks /workspaces, 503 while shutting down, or 401 when auth is required.

Common situations: A legacy/stale server from an older version running on the port (missing endpoint); server mid-shutdown; proxy intercepting with a non-200 challenge page; version mismatch between client and daemon.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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