{"record":{"id":"4ed7ff2320586f5d","repo":"charmbracelet/crush","slug":"errors-new-s-error","errorCode":null,"errorMessage":"errors.New(s.Error)","messagePattern":"errors\\.New\\(s\\.Error\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/workspace/client_workspace.go","lineNumber":1389,"sourceCode":"\t}\n}\n\n// protoToSkillStates reconstructs internal skill state slices from\n// their wire representation. Non-empty Error strings are turned into\n// synthetic error values; the TUI never type-asserts on Err.\nfunc protoToSkillStates(in []proto.SkillState) []*skills.SkillState {\n\tif len(in) == 0 {\n\t\treturn nil\n\t}\n\tout := make([]*skills.SkillState, len(in))\n\tfor i, s := range in {\n\t\tstate := &skills.SkillState{\n\t\t\tName:  s.Name,\n\t\t\tPath:  s.Path,\n\t\t\tState: skills.DiscoveryState(s.State),\n\t\t}\n\t\tif s.Error != \"\" {\n\t\t\tstate.Err = errors.New(s.Error)\n\t\t}\n\t\tout[i] = state\n\t}\n\treturn out\n}\n\nfunc todosToProto(todos []session.Todo) []proto.Todo {\n\tif len(todos) == 0 {\n\t\treturn nil\n\t}\n\tout := make([]proto.Todo, len(todos))\n\tfor i, t := range todos {\n\t\tout[i] = proto.Todo{\n\t\t\tContent:    t.Content,\n\t\t\tStatus:     string(t.Status),\n\t\t\tActiveForm: t.ActiveForm,\n\t\t}\n\t}","sourceCodeStart":1371,"sourceCodeEnd":1407,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/workspace/client_workspace.go#L1371-L1407","documentation":"Not a distinct error string but a re-wrapping site: when a skill's discovery state carries a non-empty Error string, the workspace converts it into a Go error via errors.New(s.Error) and attaches it to the SkillState. The actual message text comes from the skill loader/discovery layer.","triggerScenarios":"Listing/updating skills where one or more skill files failed discovery — malformed frontmatter, unreadable path, invalid SKILL.md — so s.Error is populated and gets converted here.","commonSituations":"Skill file with broken YAML frontmatter; skill directory removed after registration (permission denied); a skill schema mismatch after a Crush version upgrade.","solutions":["Read state.Err's message to find the failing skill and fix its SKILL.md/frontmatter","Verify the skill path exists and is readable","Re-run the skills command after fixing; disable the broken skill if it's third-party"],"exampleFix":"// before\nif s.Error != \"\" {\n\tstate.Err = errors.New(s.Error)\n}\n// after\nif s.Error != \"\" {\n\tstate.Err = fmt.Errorf(\"skill %q: %w\", s.Name, errors.New(s.Error))\n}","handlingStrategy":"type-guard","validationCode":"for _, s := range skillStates {\n\tif s.Error != \"\" {\n\t\tlog.Printf(\"skill %s failed discovery: %s\", s.Name, s.Error)\n\t}\n}","typeGuard":"func (st *skills.SkillState) HasError() bool { return st.Err != nil }","tryCatchPattern":"if state.Err != nil {\n\tvar skillErr *os.PathError\n\tif errors.As(state.Err, &skillErr) {\n\t\t// handle missing/unreadable skill file\n\t}\n}","preventionTips":["Validate skill SKILL.md frontmatter before installing","Keep skill directories readable and present","Re-check skill health after Crush upgrades or path changes"],"tags":["skills","workspace","error-wrapping","go"],"backgroundTag":"skill-discovery-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}