sipeed/picoclaw · error

unsupported change_kind %q

Error message

unsupported change_kind %q

What it means

renderAppliedBody's switch default (pkg/evolution/apply.go:213): draft.ChangeKind is not one of the four supported constants ("create", "append", "replace", "merge" from types.go). The Go zero value "" also lands here, so an unset ChangeKind is rejected rather than defaulted.

Source

Thrown at pkg/evolution/apply.go:213

		}
		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
		}
		mergedSection := strings.Join([]string{
			"",
			"## Merged Knowledge",
			strings.TrimSpace(patch),
			"",
		}, "\n")
		return strings.TrimRight(existingBody, "\n") + mergedSection, nil
	default:
		return "", fmt.Errorf("unsupported change_kind %q", draft.ChangeKind)
	}
}

func renderDeployablePatchBody(body, targetSkillName string) (string, error) {
	body = renderDeployableSkillBody(body)
	frontmatter, markdownBody := splitSkillFrontmatter(body)
	if frontmatter == "" {
		markdownBody = body
	} else {
		fields, err := parseSkillFrontmatterFields(frontmatter, true)
		if err != nil {
			return "", err
		}
		if name := strings.TrimSpace(fields["name"]); name != "" && name != targetSkillName {
			return "", fmt.Errorf(
				"skill patch frontmatter name %q does not match target skill %q",
				name,
				targetSkillName,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Set ChangeKind to evolution.ChangeKindCreate / Append / Replace / Merge exactly
  2. Validate ChangeKind against the four constants before calling ApplyDraft
  3. Purge or re-save stale drafts in skill_drafts.json after upgrading or downgrading
  4. Reject unknown change_kind values at draft load time

Example fix

// before
draft := evolution.SkillDraft{TargetSkillName: "x", BodyOrPatch: body} // ChangeKind == ""

// after
draft := evolution.SkillDraft{TargetSkillName: "x", BodyOrPatch: body, ChangeKind: evolution.ChangeKindCreate}
Defensive patterns

Strategy: validation

Validate before calling

func supportedChangeKind(k evolution.ChangeKind) bool {
	switch k {
	case evolution.ChangeKindCreate, evolution.ChangeKindAppend,
		evolution.ChangeKindReplace, evolution.ChangeKindMerge:
		return true
	}
	return false
}

if !supportedChangeKind(draft.ChangeKind) {
	// reject or default before calling ApplyDraft

Type guard

func isValidChangeKind(k evolution.ChangeKind) bool {
	switch k {
	case evolution.ChangeKindCreate, evolution.ChangeKindAppend,
		evolution.ChangeKindReplace, evolution.ChangeKindMerge:
		return true
	}
	return false
}

Try / catch

if err := applier.ApplyDraft(ctx, ws, draft); err != nil {
	if strings.Contains(err.Error(), "unsupported change_kind") {
		// draft came from a newer/older store — re-save or drop it
	}
}

Prevention

When it happens

Trigger: A SkillDraft constructed without setting ChangeKind (zero value ""); ChangeKind loaded from the persisted drafts store (skill_drafts.json) with a typo'd or new value such as "update" or "Create"; drafts written by a newer package version using a kind this binary does not know.

Common situations: Hand-built drafts in tests or tools forgetting the field; version skew between the writer and reader of the drafts store; serialized enum renamed across releases.

Related errors


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