sipeed/picoclaw · error

resolve lifecycle delete skill name: %w

Error message

resolve lifecycle delete skill name: %w

What it means

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.

Source

Thrown at pkg/evolution/lifecycle.go:56

	}

	return profile.Status
}

func ApplyLifecycleState(paths Paths, profile SkillProfile, next SkillStatus) error {
	if next != SkillStatusDeleted {
		return nil
	}

	workspace := profile.WorkspaceID
	if workspace == "" {
		workspace = inferWorkspaceFromPaths(paths)
	}
	if workspace == "" {
		return fmt.Errorf("resolve lifecycle delete workspace for skill %q: workspace is required", profile.SkillName)
	}
	if err := skills.ValidateSkillName(profile.SkillName); err != nil {
		return fmt.Errorf("resolve lifecycle delete skill name: %w", err)
	}

	skillPath := filepath.Join(workspace, "skills", profile.SkillName, "SKILL.md")
	err := os.Remove(skillPath)
	if errors.Is(err, os.ErrNotExist) {
		return nil
	}
	return err
}

func RunLifecycleOnce(store *Store, paths Paths, workspace string, now time.Time) (LifecycleRunSummary, error) {
	if store == nil {
		return LifecycleRunSummary{}, nil
	}

	profiles, err := store.LoadProfiles()
	if err != nil {
		return LifecycleRunSummary{}, err

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. 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
  2. Delete the corrupt/stale profile record so it stops blocking lifecycle runs
  3. Backfill and normalize skill names during migration

Example fix

// before
profile.SkillName = "my_skill" // rejected: underscore

// after
profile.SkillName = "my-skill" // matches skills/my-skill/SKILL.md
Defensive patterns

Strategy: validation

Validate before calling

if err := skills.ValidateSkillName(profile.SkillName); err != nil {
	// fix or drop the profile before running the lifecycle
}

Type guard

func isValidSkillName(name string) bool {
	return name != "" && !filepath.IsAbs(name) && len(name) <= 64 &&
		regexp.MustCompile(`^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$`).MatchString(name)
}

Try / catch

if err := evolution.ApplyLifecycleState(paths, profile, evolution.SkillStatusDeleted); err != nil {
	if strings.Contains(err.Error(), "resolve lifecycle delete skill name") {
		// normalize profile.SkillName to the slug rules matching the on-disk directory
	}
}

Prevention

When it happens

Trigger: 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.

Common situations: Legacy profiles created before strict naming; profiles hand-imported from another tool; partially written profile files; workspace migrations renaming skills outside the slug rules.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/60c302880e613b71. Report an issue: GitHub.