sipeed/picoclaw · error
cannot replace skill %q: skill does not exist
Error message
cannot replace skill %q: skill does not exist
What it means
renderAppliedBody (pkg/evolution/apply.go:185) refuses change_kind "replace" when the target skill does not exist on disk (backupCurrentSkill returned hadOriginal=false). Replace is only defined for an existing skill because it must back up the current body first. No side effects occur — the error precedes any write.
Source
Thrown at pkg/evolution/apply.go:185
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)
if err != nil {
return "", err
}
if !hadOriginal || strings.TrimSpace(existingBody) == "" {
return renderDeployableSkillBody(draft.BodyOrPatch), nilView on GitHub (pinned to 49183d7e8d)
Solutions
- Switch the draft's ChangeKind to create if the skill should be (re)created from scratch
- Restore the skill from the newest backup under paths.BackupsDir, then re-apply the replace
- Fix TargetSkillName to point at an existing skill
Example fix
// before draft.ChangeKind = evolution.ChangeKindReplace // skill missing on disk // after draft.ChangeKind = evolution.ChangeKindCreate
Defensive patterns
Strategy: validation
Validate before calling
if draft.ChangeKind == evolution.ChangeKindReplace {
if _, err := os.Stat(filepath.Join(ws, "skills", draft.TargetSkillName, "SKILL.md")); os.IsNotExist(err) {
// switch to ChangeKindCreate or restore the skill from paths.BackupsDir first
}
} Try / catch
if err := applier.ApplyDraft(ctx, ws, draft); err != nil {
if strings.Contains(err.Error(), "skill does not exist") {
// use ChangeKindCreate, or restore the target skill, then retry
}
} Prevention
- Re-check target existence immediately before applying replace drafts — targets get deleted by lifecycle or coworkers
- Keep backups under paths.BackupsDir so a deleted target can be restored for a pending replace
- Expire or re-validate stale drafts after workspace resets
When it happens
Trigger: ApplyDraft with ChangeKindReplace on a fresh workspace where <workspace>/skills/<name>/SKILL.md was never created; the target was deleted manually or by the lifecycle (archived->deleted) while the replace draft was pending; TargetSkillName typo pointing at a directory that never existed.
Common situations: Workspace reset or skills directory wiped between draft generation and apply; lifecycle garbage collection removing skills with pending drafts; renamed skills invalidating stored drafts.
Related errors
- cannot create skill %q: skill already exists
- resolve lifecycle delete skill name: %w
- session already completed
- process hook %q is closed
- process hook %q closed while waiting for %s
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/de062a17ea257b3e.
Report an issue: GitHub.