charmbracelet/crush · error

failed to decode session history files: %w

Error message

failed to decode session history files: %w

What it means

ListSessionHistoryFiles got HTTP 200 but the response body could not be decoded into []proto.File. The JSON decode error is wrapped with this message. Typically the body is malformed, truncated, or shaped differently than the expected array of File objects.

Source

Thrown at internal/client/proto.go:636

	if err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {
		return nil, fmt.Errorf("failed to decode session: %w", err)
	}
	return &sess, nil
}

// ListSessionHistoryFiles retrieves history files for a session as proto types.
func (c *Client) ListSessionHistoryFiles(ctx context.Context, id string, sessionID string) ([]proto.File, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/sessions/%s/history", id, sessionID), nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get session history files: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get session history files: status code %d", rsp.StatusCode)
	}
	var files []proto.File
	if err := json.NewDecoder(rsp.Body).Decode(&files); err != nil {
		return nil, fmt.Errorf("failed to decode session history files: %w", err)
	}
	return files, nil
}

// CreateSession creates a new session in a workspace as a proto type.
func (c *Client) CreateSession(ctx context.Context, id string, title string) (*proto.Session, error) {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/sessions", id), nil, jsonBody(proto.Session{Title: title}), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return nil, fmt.Errorf("failed to create session: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to create 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

  1. Log the wrapped error to see the exact decode failure.
  2. curl the endpoint and inspect the raw body to confirm the JSON shape.
  3. Align server and client versions so the File schema matches proto.File.
  4. Check for proxies/middleware rewriting responses.
  5. Retry on truncated (unexpected EOF) responses.
Defensive patterns

Strategy: try-catch

Type guard

func isHistoryDecodeError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to decode session history files")
}

Try / catch

files, err := client.ListSessionHistoryFiles(ctx, wsID, sessID)
if err != nil {
    if isHistoryDecodeError(err) {
        // inspect raw server response / align versions, then retry
        return fmt.Errorf("unparseable history payload: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: History endpoint returns 200 with a non-array JSON document, truncated body, empty body, or File fields whose types changed between server/client versions.

Common situations: Proxy returning 200 with HTML; server version skew changing the File schema; server returning null or an object 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/3cd515601dcc2c62. Report an issue: GitHub.