sipeed/picoclaw · error

skill heading is required

Error message

skill heading is required

What it means

validateAppliedSkillBody requires the skill body to contain a line-anchored H1: the literal substring "\n# ". Frontmatter alone is not enough - the body must also carry an ATX H1 heading (which becomes the skill document's title). Note the check: '## Subheading' does not match, '#Heading' without a space does not match, and a heading is found only when preceded by a newline (the frontmatter's closing '---' line supplies one).

Source

Thrown at pkg/evolution/apply.go:152

		return err
	}
	return nil
}

func isDirNotEmptyError(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(strings.ToLower(err.Error()), "directory not empty")
}

func validateAppliedSkillBody(body, targetSkillName string, allowExtraFrontmatterFields bool) error {
	body = strings.TrimSpace(body)
	if !strings.HasPrefix(body, "---\n") {
		return fmt.Errorf("skill frontmatter is required")
	}
	if !strings.Contains(body, "\n# ") {
		return fmt.Errorf("skill heading is required")
	}
	frontmatter, _ := splitSkillFrontmatter(body)
	fields, err := parseSkillFrontmatterFields(frontmatter, allowExtraFrontmatterFields)
	if err != nil {
		return err
	}
	name := strings.TrimSpace(fields["name"])
	if name == "" {
		return fmt.Errorf("skill frontmatter name is required")
	}
	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
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Add an H1 line after the frontmatter, e.g. '# Skill Name' with a space after the hash
  2. If you only have '##' headings, promote the first one to '#'
  3. Verify locally: strings.Contains(body, "\\n# ") before applying

Example fix

# before: frontmatter + only H2 headings
---
name: deploy
description: Deploy the app
---
## Build
...
## Ship
...

# after: H1 present
---
name: deploy
description: Deploy the app
---
# Deploy
## Build
...
## Ship
...
Defensive patterns

Strategy: validation

Validate before calling

func hasH1(body string) bool {
    return strings.Contains(strings.ReplaceAll(body, "\r\n", "\n"), "\n# ")
}

Try / catch

if err := apply.Change(body); err != nil {
    if strings.Contains(err.Error(), "skill heading is required") {
        return fmt.Errorf("skill body needs a top-level '# ' heading: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Applying a skill whose body is only frontmatter plus paragraphs or bullet lists; a body that uses only '##' subheadings; a heading written without a space after the hash ('#Title'); content where the only H1 was deleted during editing.

Common situations: Authors structuring skill docs purely with H2s; diff/patch tooling that strips what looks like a duplicate title; generated content from templates that omit the H1 when a title field exists in frontmatter.

Related errors


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