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), nil

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Switch the draft's ChangeKind to create if the skill should be (re)created from scratch
  2. Restore the skill from the newest backup under paths.BackupsDir, then re-apply the replace
  3. 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

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


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