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

  1. Log the wrapped decode error for the exact cause.
  2. curl the endpoint and inspect the raw JSON shape.
  3. Align server and client versions on the Session schema.
  4. Check proxies/middleware rewriting responses.
  5. 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

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

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/d7ebddaa3a09b8e2. Report an issue: GitHub.