charmbracelet/crush · error
failed to decode sessions: %w
Error message
failed to decode sessions: %w
What it means
ListSessions received HTTP 200 but the body could not be decoded into []proto.Session. The JSON decode error is wrapped with this message. The response was not a valid JSON array of sessions with the expected field types.
Source
Thrown at internal/client/proto.go:670
if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
return nil, fmt.Errorf("failed to decode session: %w", err)
}
return &sess, nil
}
// ListSessions lists all sessions in a workspace as proto types.
func (c *Client) ListSessions(ctx context.Context, id string) ([]proto.Session, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/sessions", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get sessions: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get sessions: status code %d", rsp.StatusCode)
}
var sessions []proto.Session
if err := json.NewDecoder(rsp.Body).Decode(&sessions); err != nil {
return nil, fmt.Errorf("failed to decode sessions: %w", err)
}
return sessions, nil
}
// GrantPermission grants a permission on a workspace. The returned
// bool reports whether this call resolved the pending request (true)
// or found it already resolved by a previous caller (false). A false
// value is not an error — it just means another subscriber resolved
// the same request first.
func (c *Client) GrantPermission(ctx context.Context, id string, req proto.PermissionGrant) (bool, error) {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/permissions/grant", id), nil, jsonBody(req), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return false, fmt.Errorf("failed to grant permission: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to grant permission: status code %d", rsp.StatusCode)
}View on GitHub (pinned to 7944b8e522)
Solutions
- Log the wrapped decode error for the exact cause.
- curl the endpoint and inspect the raw JSON shape.
- Align server and client versions on the Session schema.
- Check proxies/middleware rewriting responses.
- Retry on truncated responses.
Defensive patterns
Strategy: try-catch
Type guard
func isSessionsDecodeError(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to decode sessions")
} Try / catch
sessions, err := client.ListSessions(ctx, wsID)
if err != nil {
if isSessionsDecodeError(err) {
// 200 with bad body: align schema versions and retry
return fmt.Errorf("unparseable sessions payload: %w", err)
}
return err
} Prevention
- Keep server and client Session schemas in sync.
- Inspect raw endpoint output with curl during integration.
- Watch for proxies returning 200 with non-JSON bodies.
- Retry on truncation-style decode errors.
When it happens
Trigger: Sessions endpoint returns 200 with a JSON object instead of an array, a truncated body, an empty body, or Session fields whose types changed between versions.
Common situations: Proxy returning 200 with an HTML page; server/client schema skew on Session; server bug emitting null instead of an array.
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 session: %w
- failed to decode session history files: %w
- server health check failed: %s
- server shutdown failed: %s
- failed to set compact mode: status code %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/d7ebddaa3a09b8e2.
Report an issue: GitHub.