charmbracelet/crush · error
failed to decode workspace: %w
Error message
failed to decode workspace: %w
What it means
CreateWorkspace performed a successful HTTP POST to /workspaces (status passed checkStatus), but the response body could not be decoded into proto.Workspace via encoding/json. This means the server returned a 2xx response whose body is not the expected workspace JSON shape. The library wraps the decode error with %w so the underlying json error (syntax or type mismatch) is preserved.
Source
Thrown at internal/client/proto.go:53
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)
}
var created proto.Workspace
if err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {
return nil, fmt.Errorf("failed to decode workspace: %w", err)
}
return &created, nil
}
// GetWorkspace retrieves a workspace from the server.
func (c *Client) GetWorkspace(ctx context.Context, id string) (*proto.Workspace, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get workspace: %w", err)
}
defer rsp.Body.Close()
if err := checkStatus(rsp); err != nil {
return nil, fmt.Errorf("failed to get workspace: %w", err)
}
var ws proto.Workspace
if err := json.NewDecoder(rsp.Body).Decode(&ws); err != nil {
return nil, fmt.Errorf("failed to decode workspace: %w", err)
}View on GitHub (pinned to 7944b8e522)
Solutions
- Verify client and server are the same crush version (upgrade or rebuild both).
- Log the raw response body (e.g. io.ReadAll before decode) to see what the server actually returned.
- Check that the server URL points at the real crush server, not a proxy/other service that returns HTML with 200.
- Retry the create; a transient truncation may succeed on a second attempt.
- Inspect the server logs for errors producing a malformed response.
Example fix
// before: decode blindly
var created proto.Workspace
if err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {
return nil, fmt.Errorf("failed to decode workspace: %w", err)
}
// after: surface the body for diagnosis
body, _ := io.ReadAll(rsp.Body)
var created proto.Workspace
if err := json.Unmarshal(body, &created); err != nil {
return nil, fmt.Errorf("failed to decode workspace: %w (body: %s)", err, body)
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: confirm the endpoint returns JSON
rsp, err := http.Get(serverURL + "/workspaces")
if err != nil || !strings.Contains(rsp.Header.Get("Content-Type"), "application/json") {
return fmt.Errorf("server does not speak JSON at %s", serverURL)
} Try / catch
created, err := client.CreateWorkspace(ctx, ws)
if err != nil {
var urlErr *url.Error
switch {
case errors.As(err, &urlErr):
return fmt.Errorf("transport failure: %w", urlErr)
case strings.Contains(err.Error(), "failed to decode"):
return fmt.Errorf("server/client version mismatch or bad response: %w", err)
default:
return err
}
} Prevention
- Pin client and server to the same build/version.
- Bypass or log through proxies when debugging.
- Log raw response bodies on decode failure.
- Add a smoke test that creates a workspace against a live server.
When it happens
Trigger: Calling client.CreateWorkspace when the server returns a non-JSON or structurally different 2xx body: empty body, HTML error page from a proxy, truncated response, or a server version whose Workspace JSON fields have incompatible types for the client's proto.Workspace struct.
Common situations: Client and server (crush serve) built from different versions where the Workspace schema drifted; a reverse proxy or captive portal intercepting requests; server crashing mid-response leaving a truncated body; pointing the client at a wrong port hosting a non-crush HTTP service that returns 200 with HTML.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode skills: %w
- failed to decode skill response: %w
- failed to get workspace: %w
- failed to delete workspace: %w
- failed to delete workspace: status code %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/3b2aa6b99cf2b817.
Report an issue: GitHub.