charmbracelet/crush · error
failed to list skills: status code
Error message
failed to list skills: status code %d
What it means
Returned by Client.ListSkills when the HTTP request succeeded but the server responded with a status code other than 200. The library expects a 200 with a JSON array of proto.SkillInfo; any non-200 (401 unauthorized, 403 forbidden, 404 unknown workspace, 500 server error) triggers this error. The status code is embedded in the message since the body is not surfaced.
Solutions
- Parse the status code from the error message to identify the class of failure (4xx vs 5xx).
- For 401/403, refresh credentials or check the workspace access permissions for the current token.
- For 404, verify the workspace ID exists via ListWorkspaces before calling ListSkills.
- For 5xx, check server logs and retry after the server recovers.
- Confirm the client and server versions are compatible (endpoint still exists).
Example fix
// before
if err != nil { log.Fatal(err) } // "failed to list skills: status code 404"
// after
ws, err := client.GetWorkspace(ctx, wsID)
if err != nil || ws == nil {
return fmt.Errorf("workspace %s not found, cannot list skills", wsID)
}
skills, err := client.ListSkills(ctx, wsID) Defensive patterns
Strategy: validation
Validate before calling
// Verify the workspace exists before listing skills
ws, err := client.GetWorkspace(ctx, wsID)
if err != nil || ws == nil {
return fmt.Errorf("workspace %q unavailable", wsID)
} Type guard
func isNon200(err error) (code int, ok bool) {
m := regexp.MustCompile(`status code (\d+)`).FindStringSubmatch(err.Error())
if len(m) < 2 { return 0, false }
code, err2 := strconv.Atoi(m[1])
return code, err2 == nil
} Try / catch
skills, err := client.ListSkills(ctx, wsID)
if err != nil {
if code, ok := isNon200(err); ok {
switch {
case code == 401 || code == 403: reauth()
case code == 404: return ErrWorkspaceNotFound
default: return retryLater(err)
}
}
return err
} Prevention
- Validate workspace IDs against ListWorkspaces before use.
- Refresh auth tokens before long-running flows.
- Keep client and server versions in sync.
- Treat 5xx as retryable with backoff.
When it happens
Trigger: Calling ListSkills with a workspace ID that does not exist (404), an expired or missing auth token (401/403), or when the server has an internal error or is behind a misconfigured reverse proxy (502/503).
Common situations: Stale API credentials after token rotation; typo'd or deleted workspace ID; server version mismatch where the /workspaces/{id}/skills endpoint moved; load balancer returning 502 during deploys.
Related errors
- failed to read skill: status code
- failed to disable docker MCP: status code
- failed to enable docker MCP: status code
- failed to answer question batch: status code
- failed to cancel question batch: status code
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/175ac83ce3970dd5.
Report an issue: GitHub.
Appendix: source
Thrown at internal/client/config.go:227
}
var result struct {
Prompt string `json:"prompt"`
}
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 {View on GitHub (pinned to 7944b8e522)