charmbracelet/crush · error
failed to decode skills: %w
Error message
failed to decode skills: %w
What it means
Returned by Client.ListSkills when the server responded 200 OK but the response body could not be decoded into []proto.SkillInfo. The server sent malformed JSON, an unexpected shape (e.g. an object wrapping the array, or an error payload with a 200 status), or an empty/truncated body. This is a client/server contract mismatch.
Source
Thrown at internal/client/config.go:231
if err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("failed to decode init prompt response: %w", err)
}
return result.Prompt, nil
}
// ListSkills retrieves the visible skills for a workspace.
func (c *Client) ListSkills(ctx context.Context, id string) ([]proto.SkillInfo, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/skills", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to list skills: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to list skills: status code %d", rsp.StatusCode)
}
var skills []proto.SkillInfo
if err := json.NewDecoder(rsp.Body).Decode(&skills); err != nil {
return nil, fmt.Errorf("failed to decode skills: %w", err)
}
return skills, nil
}
// ReadSkill reads a skill's content by ID from the server.
func (c *Client) ReadSkill(ctx context.Context, id, skillID string) (*proto.ReadSkillResponse, error) {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/skills/read", id), nil, jsonBody(proto.ReadSkillRequest{
SkillID: skillID,
}), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return nil, fmt.Errorf("failed to read skill: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to read skill: status code %d", rsp.StatusCode)
}
var result proto.ReadSkillResponse
if err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {View on GitHub (pinned to 7944b8e522)
Solutions
- Dump the raw response body (e.g. with a debug HTTP proxy or by checking server logs) to see what was actually returned.
- Verify the server's skills endpoint returns a JSON array matching proto.SkillInfo's json tags.
- Check for client/server version skew and upgrade the mismatched side.
- Bypass any proxy/middleware to confirm it isn't rewriting the response.
- Validate the payload with curl | jq to confirm it is a well-formed array.
Example fix
// before
skills, err := client.ListSkills(ctx, wsID) // fails: cannot unmarshal object into []SkillInfo
// after
// Confirm server shape first:
// curl -s $BASE/workspaces/$WS/skills | jq 'type' -> must be "array"
skills, err := client.ListSkills(ctx, wsID)
if err != nil {
return fmt.Errorf("server returned unexpected skills payload; check client/server versions: %w", err)
} Defensive patterns
Strategy: type-guard
Type guard
func isDecodeError(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to decode skills")
}
func isValidSkillList(skills []proto.SkillInfo) bool {
for _, s := range skills { if s.ID == "" { return false } }
return true
} Try / catch
skills, err := client.ListSkills(ctx, wsID)
if err != nil {
if isDecodeError(err) {
// contract mismatch: log raw payload via server logs, degrade gracefully
return emptySkillsFallback()
}
return err
} Prevention
- Keep client proto structs in sync with the server API.
- Smoke-test endpoints with curl | jq after server upgrades.
- Watch for proxies that replace JSON bodies with HTML pages.
- Add contract/golden tests for the skills endpoint.
When it happens
Trigger: Server returns 200 with HTML (proxy error page), an empty body, a JSON object instead of an array, or field types that don't match proto.SkillInfo's JSON schema.
Common situations: Reverse proxy intercepting the request and returning a 200 HTML page; server-side API version change renaming or retyping SkillInfo fields; middleware writing extra content to the response body.
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 skill response: %w
- failed to list skills: %w
- failed to list skills: status code %d
- failed to read skill: %w
- failed to read skill: status code %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/aab85364010bf5dc.
Report an issue: GitHub.