sipeed/picoclaw · error

unsupported skill frontmatter field %q

Error message

unsupported skill frontmatter field %q

What it means

parseSkillFrontmatterFields (pkg/evolution/apply.go:267) rejects frontmatter keys other than name/description when allowExtraFields is false. allowExtraFields comes from allowsExistingFrontmatterFields: true only when the target skill already exists on disk AND change_kind is append or merge. So create and replace always require exactly name+description, and append/merge to a not-yet-existing skill do too.

Source

Thrown at pkg/evolution/apply.go:267

		}
	}
	if end < 0 {
		return "", body
	}
	return strings.Join(lines[1:end], "\n"), strings.TrimLeft(strings.Join(lines[end+1:], "\n"), "\n")
}

func parseSkillFrontmatterFields(frontmatter string, allowExtraFields bool) (map[string]string, error) {
	var raw map[string]any
	if err := yaml.Unmarshal([]byte(frontmatter), &raw); err != nil {
		return nil, fmt.Errorf("invalid skill frontmatter: %w", err)
	}
	for key := range raw {
		if key != "name" && key != "description" {
			if allowExtraFields {
				continue
			}
			return nil, fmt.Errorf("unsupported skill frontmatter field %q", key)
		}
	}

	var typed struct {
		Name        string `yaml:"name"`
		Description string `yaml:"description"`
	}
	if err := yaml.Unmarshal([]byte(frontmatter), &typed); err != nil {
		return nil, fmt.Errorf("invalid skill frontmatter: %w", err)
	}
	return map[string]string{
		"name":        typed.Name,
		"description": typed.Description,
	}, nil
}

func stripLeadingH1(body string) string {
	lines := strings.Split(strings.TrimLeft(body, "\n"), "\n")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Strip the draft frontmatter down to only name and description
  2. Use append/merge against the existing skill — extra keys already in the existing file's frontmatter are then allowed
  3. Store extra metadata outside SKILL.md (e.g. in the evolution profile) instead of frontmatter

Example fix

# before (create draft)
---
name: deploy-checks
description: ...
version: 2
metadata:
  owner: platform
---

# after
---
name: deploy-checks
description: ...
---
Defensive patterns

Strategy: validation

Validate before calling

func frontmatterKeysAllowed(body string, allowExtra bool) error {
	var raw map[string]any
	if err := yaml.Unmarshal([]byte(extractFrontmatter(body)), &raw); err != nil {
		return err
	}
	for k := range raw {
		if k != "name" && k != "description" && !allowExtra {
			return fmt.Errorf("unsupported frontmatter field %q", k)
		}
	}
	return nil
}

// allowExtra mirrors allowsExistingFrontmatterFields:
// hadOriginal && (kind == append || kind == merge)

Try / catch

if err := applier.ApplyDraft(ctx, ws, draft); err != nil {
	if strings.Contains(err.Error(), "unsupported skill frontmatter field") {
		// strip the named key, or switch to append/merge onto the existing skill
	}
}

Prevention

When it happens

Trigger: Create/replace draft with extra keys such as `version:`, `metadata:`, `tags:`, `license:`; append draft where the target skill does not exist (hadOriginal=false); replace draft — the existing skill's richer frontmatter is discarded and the new body must be minimal.

Common situations: LLM adding metadata fields beyond the prompt's two-key contract; authors copying frontmatter from other agent-skill formats that allow `metadata:`; trying to upgrade a hand-written skill with extra fields via a replace draft.

Related errors


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