charmbracelet/crush · error
failed to decode messages: %w
Error message
failed to decode messages: %w
What it means
Thrown by Client.ListMessages when the 200 response body from GET /workspaces/{id}/sessions/{sessionID}/messages cannot be JSON-decoded into []proto.Message. Unlike GetSessionInfo, an empty body (io.EOF) is tolerated, so this error means the body was non-empty but malformed or structurally incompatible with proto.Message. Called via restoreModelFromSession.
Source
Thrown at internal/client/proto.go:602
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to initiate session agent processing: status code %d", rsp.StatusCode)
}
return nil
}
// ListMessages retrieves all messages for a session as proto types.
func (c *Client) ListMessages(ctx context.Context, id string, sessionID string) ([]proto.Message, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/sessions/%s/messages", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get messages: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get messages: status code %d", rsp.StatusCode)
}
var msgs []proto.Message
if err := json.NewDecoder(rsp.Body).Decode(&msgs); err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("failed to decode messages: %w", err)
}
return msgs, nil
}
// GetSession retrieves a specific session as a proto type.
func (c *Client) GetSession(ctx context.Context, id string, sessionID string) (*proto.Session, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/sessions/%s", id, sessionID), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get session: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get session: status code %d", rsp.StatusCode)
}
var sess proto.Session
if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
return nil, fmt.Errorf("failed to decode session: %w", err)
}View on GitHub (pinned to 7944b8e522)
Solutions
- Use errors.As to extract json.UnmarshalTypeError and identify which field of proto.Message is incompatible.
- Align client and server versions so the Message JSON schema matches.
- Bypass any proxy/middlebox and curl the endpoint directly to see the raw body.
- If old rows are the cause, migrate or discard the incompatible session data on the server.
Example fix
// before
msgs, err := client.ListMessages(ctx, wsID, sessionID)
if err != nil {
return err
}
// after: narrow down the incompatible field
msgs, err := client.ListMessages(ctx, wsID, sessionID)
if err != nil {
var ute *json.UnmarshalTypeError
if errors.As(err, &ute) {
return fmt.Errorf("message field %q has wrong type (version skew?): %w", ute.Field, err)
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// Probe the endpoint and verify the body parses as a message array
rsp, err := http.Get(serverURL + "/workspaces/" + wsID + "/sessions/" + sessionID + "/messages")
if err == nil && rsp.StatusCode == 200 {
var probe []json.RawMessage
if json.NewDecoder(rsp.Body).Decode(&probe) != nil {
return errors.New("server returned malformed messages payload — check version skew")
}
rsp.Body.Close()
} Type guard
func isMessageSchemaError(err error) (string, bool) {
var ute *json.UnmarshalTypeError
if errors.As(err, &ute) {
return ute.Field, true
}
return "", false
} Try / catch
msgs, err := client.ListMessages(ctx, wsID, sessionID)
if err != nil {
if field, ok := isMessageSchemaError(err); ok {
return fmt.Errorf("incompatible message schema at field %q — upgrade client/server to matching versions", field)
}
return err
} Prevention
- Keep client and server builds in lockstep — proto.Message field types are the contract
- Extract the json.UnmarshalTypeError to identify the offending field
- Test restore paths against both old-format and new-format session data
- Bypass proxies/antivirus when diagnosing persistent decode failures
When it happens
Trigger: Server returns 200 with a corrupted/truncated messages payload; a proxy rewrites the body (e.g. HTML error injected); server schema change where a field type no longer matches proto.Message (json.UnmarshalTypeError); binary garbage instead of JSON.
Common situations: Client/server version skew after an upgrade changed the message schema; middlebox (antivirus/proxy) mangling responses; DB rows written by an older version that no longer unmarshal into the current proto.Message type; disk corruption on the server.
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 workspaces: %w
- failed to decode response: %w
- failed to decode MCP resource: %w
- failed to decode MCP prompts: %w
- failed to decode MCP prompt response: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/4847a4d10e7b4153.
Report an issue: GitHub.