{"record":{"id":"3b2aa6b99cf2b817","repo":"charmbracelet/crush","slug":"failed-to-decode-workspace-w","errorCode":null,"errorMessage":"failed to decode workspace: %w","messagePattern":"failed to decode workspace: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":53,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to decode workspaces: %w\", err)\n\t}\n\treturn workspaces, nil\n}\n\n// CreateWorkspace creates a new workspace on the server.\nfunc (c *Client) CreateWorkspace(ctx context.Context, ws proto.Workspace) (*proto.Workspace, error) {\n\tws.ClientID = c.clientID\n\trsp, err := c.post(ctx, \"/workspaces\", nil, jsonBody(ws), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create workspace: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif err := checkStatus(rsp); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create workspace: %w\", err)\n\t}\n\tvar created proto.Workspace\n\tif err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode workspace: %w\", err)\n\t}\n\treturn &created, nil\n}\n\n// GetWorkspace retrieves a workspace from the server.\nfunc (c *Client) GetWorkspace(ctx context.Context, id string) (*proto.Workspace, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s\", id), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get workspace: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif err := checkStatus(rsp); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get workspace: %w\", err)\n\t}\n\tvar ws proto.Workspace\n\tif err := json.NewDecoder(rsp.Body).Decode(&ws); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode workspace: %w\", err)\n\t}","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L35-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: decode blindly\nvar created proto.Workspace\nif err := json.NewDecoder(rsp.Body).Decode(&created); err != nil {\n    return nil, fmt.Errorf(\"failed to decode workspace: %w\", err)\n}\n// after: surface the body for diagnosis\nbody, _ := io.ReadAll(rsp.Body)\nvar created proto.Workspace\nif err := json.Unmarshal(body, &created); err != nil {\n    return nil, fmt.Errorf(\"failed to decode workspace: %w (body: %s)\", err, body)\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight: confirm the endpoint returns JSON\nrsp, err := http.Get(serverURL + \"/workspaces\")\nif err != nil || !strings.Contains(rsp.Header.Get(\"Content-Type\"), \"application/json\") {\n    return fmt.Errorf(\"server does not speak JSON at %s\", serverURL)\n}","typeGuard":null,"tryCatchPattern":"created, err := client.CreateWorkspace(ctx, ws)\nif err != nil {\n    var urlErr *url.Error\n    switch {\n    case errors.As(err, &urlErr):\n        return fmt.Errorf(\"transport failure: %w\", urlErr)\n    case strings.Contains(err.Error(), \"failed to decode\"):\n        return fmt.Errorf(\"server/client version mismatch or bad response: %w\", err)\n    default:\n        return err\n    }\n}","preventionTips":["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."],"tags":["http-client","json-decode","workspace"],"backgroundTag":"json-response-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}