charmbracelet/crush · error
failed to decode workspaces: %w
Error message
failed to decode workspaces: %w
What it means
ListWorkspaces received a 200 response but the body could not be decoded into []proto.Workspace. This wraps the JSON decoding error and indicates the payload shape or encoding deviates from the expected array of workspace objects.
Source
Thrown at internal/client/proto.go:35
"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)
}
var created proto.Workspace
if err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {
return nil, fmt.Errorf("failed to decode workspace: %w", err)View on GitHub (pinned to 7944b8e522)
Solutions
- Read the wrapped json error: *json.UnmarshalTypeError signals schema drift between client and server.
- Align client and server versions so proto.Workspace matches.
- Capture the raw response body to confirm the actual payload shape.
- Check for intermediate proxies altering or truncating the response.
Example fix
// before
var workspaces []proto.Workspace
if err := json.NewDecoder(rsp.Body).Decode(&workspaces); err != nil {
return nil, fmt.Errorf("failed to decode workspaces: %w", err)
}
// after: tolerate null/absent array
body, _ := io.ReadAll(rsp.Body)
var workspaces []proto.Workspace
if len(bytes.TrimSpace(body)) > 0 {
if err := json.Unmarshal(body, &workspaces); err != nil {
return nil, fmt.Errorf("failed to decode workspaces: %w (body: %s)", err, truncate(body, 256))
}
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the endpoint returns a JSON array before relying on decode
resp, err := http.Get(baseURL + "/workspaces")
if err == nil {
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
return fmt.Errorf("unexpected content type %q from server", ct)
}
resp.Body.Close()
} Type guard
func isWorkspaceSchemaMismatch(err error) bool {
var typeErr *json.UnmarshalTypeError
return errors.As(err, &typeErr)
} Try / catch
workspaces, err := client.ListWorkspaces(ctx)
if err != nil {
var typeErr *json.UnmarshalTypeError
if errors.As(err, &typeErr) {
// Schema drift: refresh server or fall back to empty list
log.Printf("workspace schema mismatch at offset %d; restarting server", typeErr.Offset)
return restartServerAndList(ctx)
}
return err
} Prevention
- Version-lock client and server so proto.Workspace stays in sync.
- Add a startup smoke test that decodes a sample /workspaces response.
- Treat a JSON null or empty body as an empty workspace list defensively.
- Bypass or configure proxies so they do not alter response bodies.
When it happens
Trigger: Calling ListWorkspaces when the server returns 200 with a malformed or differently-shaped JSON body — a single object instead of an array, changed Workspace field types, truncated body, or non-JSON content.
Common situations: Client and server built from different versions (Workspace schema drift); a proxy injecting content into the body; server bug returning null instead of an array; manual server-side modifications.
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 MCP pending auth: %w
- failed to decode MCP auth URL: %w
- failed to decode messages: %w
- failed to decode response: %w
- failed to marshal api key string: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/ea37c8b3fcbe0427.
Report an issue: GitHub.