{"record":{"id":"60c302880e613b71","repo":"sipeed/picoclaw","slug":"resolve-lifecycle-delete-skill-name-w","errorCode":null,"errorMessage":"resolve lifecycle delete skill name: %w","messagePattern":"resolve lifecycle delete skill name: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/evolution/lifecycle.go","lineNumber":56,"sourceCode":"\t}\n\n\treturn profile.Status\n}\n\nfunc ApplyLifecycleState(paths Paths, profile SkillProfile, next SkillStatus) error {\n\tif next != SkillStatusDeleted {\n\t\treturn nil\n\t}\n\n\tworkspace := profile.WorkspaceID\n\tif workspace == \"\" {\n\t\tworkspace = inferWorkspaceFromPaths(paths)\n\t}\n\tif workspace == \"\" {\n\t\treturn fmt.Errorf(\"resolve lifecycle delete workspace for skill %q: workspace is required\", profile.SkillName)\n\t}\n\tif err := skills.ValidateSkillName(profile.SkillName); err != nil {\n\t\treturn fmt.Errorf(\"resolve lifecycle delete skill name: %w\", err)\n\t}\n\n\tskillPath := filepath.Join(workspace, \"skills\", profile.SkillName, \"SKILL.md\")\n\terr := os.Remove(skillPath)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\nfunc RunLifecycleOnce(store *Store, paths Paths, workspace string, now time.Time) (LifecycleRunSummary, error) {\n\tif store == nil {\n\t\treturn LifecycleRunSummary{}, nil\n\t}\n\n\tprofiles, err := store.LoadProfiles()\n\tif err != nil {\n\t\treturn LifecycleRunSummary{}, err","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/evolution/lifecycle.go#L38-L74","documentation":"ApplyLifecycleState (pkg/evolution/lifecycle.go:56) runs skills.ValidateSkillName before deleting: the name must be non-empty, not an absolute path, at most 64 characters, and match ^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$ — no underscores, spaces, slashes, or dots. The underlying reason is wrapped with %w. This prevents path traversal or malformed deletes from corrupt profile records.","triggerScenarios":"A stored SkillProfile with skill_name like `my_skill`, `My Skill`, `team/repo-skill`, an absolute path, a >64-char name, or an empty skill_name from truncated/corrupt profile JSON reaching the deleted transition.","commonSituations":"Legacy profiles created before strict naming; profiles hand-imported from another tool; partially written profile files; workspace migrations renaming skills outside the slug rules.","solutions":["Fix the profile's skill_name to the slug rules (lowercase alphanumerics and single hyphens, <=64 chars) to match the real on-disk skill directory","Delete the corrupt/stale profile record so it stops blocking lifecycle runs","Backfill and normalize skill names during migration"],"exampleFix":"// before\nprofile.SkillName = \"my_skill\" // rejected: underscore\n\n// after\nprofile.SkillName = \"my-skill\" // matches skills/my-skill/SKILL.md","handlingStrategy":"validation","validationCode":"if err := skills.ValidateSkillName(profile.SkillName); err != nil {\n\t// fix or drop the profile before running the lifecycle\n}","typeGuard":"func isValidSkillName(name string) bool {\n\treturn name != \"\" && !filepath.IsAbs(name) && len(name) <= 64 &&\n\t\tregexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`).MatchString(name)\n}","tryCatchPattern":"if err := evolution.ApplyLifecycleState(paths, profile, evolution.SkillStatusDeleted); err != nil {\n\tif strings.Contains(err.Error(), \"resolve lifecycle delete skill name\") {\n\t\t// normalize profile.SkillName to the slug rules matching the on-disk directory\n\t}\n}","preventionTips":["Create skills only through paths that enforce the slug pattern (alphanumerics + single hyphens, <=64 chars, no underscores/spaces/slashes)","Validate profile skill_name when loading profiles from external sources","Monitor profile stores for empty/corrupt skill_name fields"],"tags":["go","lifecycle","skills","validation","data-corruption"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}