sipeed/picoclaw · error
cannot create skill %q: skill already exists
Error message
cannot create skill %q: skill already exists
What it means
renderAppliedBody (pkg/evolution/apply.go:180) refuses change_kind "create" when backupCurrentSkill found an existing <workspace>/skills/<name>/SKILL.md. This guards idempotency: a create must never silently overwrite an already-deployed skill. The error is raised before any file write, so there are no side effects.
Source
Thrown at pkg/evolution/apply.go:180
}
if name != targetSkillName {
return fmt.Errorf("skill frontmatter name %q does not match target skill %q", name, targetSkillName)
}
if strings.TrimSpace(fields["description"]) == "" {
return fmt.Errorf("skill frontmatter description is required")
}
return nil
}
func allowsExistingFrontmatterFields(kind ChangeKind, hadOriginal bool) bool {
return hadOriginal && (kind == ChangeKindAppend || kind == ChangeKindMerge)
}
func renderAppliedBody(draft SkillDraft, existingBody string, hadOriginal bool) (string, error) {
switch draft.ChangeKind {
case ChangeKindCreate:
if hadOriginal {
return "", fmt.Errorf("cannot create skill %q: skill already exists", draft.TargetSkillName)
}
return renderDeployableSkillBody(draft.BodyOrPatch), nil
case ChangeKindReplace:
if !hadOriginal {
return "", fmt.Errorf("cannot replace skill %q: skill does not exist", draft.TargetSkillName)
}
return renderDeployableSkillBody(draft.BodyOrPatch), nil
case ChangeKindAppend:
patch, err := renderDeployablePatchBody(draft.BodyOrPatch, draft.TargetSkillName)
if err != nil {
return "", err
}
if !hadOriginal || strings.TrimSpace(existingBody) == "" {
return renderDeployableSkillBody(draft.BodyOrPatch), nil
}
return strings.TrimRight(existingBody, "\n") + "\n\n" + strings.TrimLeft(patch, "\n"), nil
case ChangeKindMerge:
patch, err := renderDeployablePatchBody(draft.BodyOrPatch, draft.TargetSkillName)View on GitHub (pinned to 49183d7e8d)
Solutions
- Change the draft's ChangeKind to replace (overwrite intent) or append/merge (additive intent)
- Delete or rename the existing <workspace>/skills/<name>/SKILL.md if a fresh create is truly intended
- Pick a different TargetSkillName for the new skill
- Filter out create drafts whose target already exists before running apply
Example fix
// before draft.ChangeKind = evolution.ChangeKindCreate // target skill already on disk // after (choose one intent) draft.ChangeKind = evolution.ChangeKindReplace // overwrite draft.ChangeKind = evolution.ChangeKindAppend // add a section
Defensive patterns
Strategy: validation
Validate before calling
func skillExists(workspace, name string) bool {
_, err := os.Stat(filepath.Join(workspace, "skills", name, "SKILL.md"))
return err == nil
}
if draft.ChangeKind == evolution.ChangeKindCreate && skillExists(ws, draft.TargetSkillName) {
// choose replace/append, rename, or skip — do not call ApplyDraft Try / catch
if err := applier.ApplyDraft(ctx, ws, draft); err != nil {
if strings.Contains(err.Error(), "skill already exists") {
// flip ChangeKind to replace/append or pick a new TargetSkillName
}
} Prevention
- Decide change kind from on-disk existence at apply time, not at draft generation time
- Make create-draft generation skip targets that already exist
- Run a single evolution writer per workspace to avoid create/create races
When it happens
Trigger: ApplyDraft with ChangeKindCreate when the target skill file exists — re-running an evolution pass after an earlier accepted create; a manually authored skill with the same name; two evolution runs racing on the same workspace.
Common situations: Retrying a pipeline run that already applied; name collisions between generated and hand-written skills; drafts generated while the skill was concurrently created by another process.
Related errors
- cannot replace skill %q: skill does not exist
- ✗ skill '%s' already installed at %s
- failed to parse legacy skills security config: %w
- skill frontmatter name %q does not match target skill %q
- skill frontmatter description is required
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/f3baf5246b44de25.
Report an issue: GitHub.