{"record":{"id":"d351192ed58beee6","repo":"charmbracelet/crush","slug":"failed-to-read-skill-status-code-d","errorCode":null,"errorMessage":"failed to read skill: status code %d","messagePattern":"failed to read skill: status code (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/config.go","lineNumber":246,"sourceCode":"\t}\n\tvar skills []proto.SkillInfo\n\tif err := json.NewDecoder(rsp.Body).Decode(&skills); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode skills: %w\", err)\n\t}\n\treturn skills, nil\n}\n\n// ReadSkill reads a skill's content by ID from the server.\nfunc (c *Client) ReadSkill(ctx context.Context, id, skillID string) (*proto.ReadSkillResponse, error) {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/skills/read\", id), nil, jsonBody(proto.ReadSkillRequest{\n\t\tSkillID: skillID,\n\t}), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read skill: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to read skill: status code %d\", rsp.StatusCode)\n\t}\n\tvar result proto.ReadSkillResponse\n\tif err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode skill response: %w\", err)\n\t}\n\treturn &result, nil\n}\n\n// MCPResourceContents holds the contents of an MCP resource.\ntype MCPResourceContents struct {\n\tURI      string `json:\"uri\"`\n\tMIMEType string `json:\"mime_type,omitempty\"`\n\tText     string `json:\"text,omitempty\"`\n\tBlob     []byte `json:\"blob,omitempty\"`\n}\n\n// EnableDockerMCP enables the Docker MCP server on the workspace.\nfunc (c *Client) EnableDockerMCP(ctx context.Context, id string) error {","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/config.go#L228-L264","documentation":"Returned by Client.ReadSkill when the POST completed but the server replied with a non-200 status code. The library requires 200 with a proto.ReadSkillResponse body; 404 (skill or workspace not found), 401/403 (auth), or 500 all produce this error. The numeric status is embedded in the message because the body is discarded.","triggerScenarios":"Calling ReadSkill with a skillID that no longer exists (404), with insufficient permissions (403), an invalid workspace ID (404), or during a server-side failure (500).","commonSituations":"Skill deleted or renamed by another agent/user before the read; listing skills, then reading from a stale cached list; expired session token; server bug returning 500 for large skill files.","solutions":["Read the status code from the error message; for 404, re-list skills to confirm the skillID is still valid.","For 401/403, re-authenticate or verify workspace permissions.","For 500, check server logs for the skills/read handler failure.","Always fetch skill IDs from a fresh ListSkills call rather than a stale cache.","Verify the workspace ID in the URL path is correct."],"exampleFix":"// before\nskill, err := client.ReadSkill(ctx, wsID, skillID) // status code 404\n// after\nskills, _ := client.ListSkills(ctx, wsID)\nif !containsSkill(skills, skillID) {\n    return fmt.Errorf(\"skill %s no longer exists\", skillID)\n}\nskill, err := client.ReadSkill(ctx, wsID, skillID)","handlingStrategy":"validation","validationCode":"// Confirm the skill still exists before reading\nskills, err := client.ListSkills(ctx, wsID)\nif err != nil { return err }\nfound := false\nfor _, s := range skills {\n    if s.ID == skillID { found = true; break }\n}\nif !found { return fmt.Errorf(\"skill %s not found\", skillID) }","typeGuard":"func isNon200(err error) (code int, ok bool) {\n    m := regexp.MustCompile(`status code (\\d+)`).FindStringSubmatch(err.Error())\n    if len(m) < 2 { return 0, false }\n    code, err2 := strconv.Atoi(m[1])\n    return code, err2 == nil\n}","tryCatchPattern":"skill, err := client.ReadSkill(ctx, wsID, skillID)\nif err != nil {\n    if code, ok := isNon200(err); ok && code == 404 {\n        return ErrSkillNotFound // handle gracefully\n    }\n    return err\n}","preventionTips":["Always source skill IDs from a fresh ListSkills call.","Handle 404 as a normal 'skill gone' case, not a crash.","Keep auth tokens valid; re-auth on 401/403.","Check server logs for 500s on the skills/read route."],"tags":["http-client","http-status","skills","api-error"],"backgroundTag":"http-non-200-response","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}