LayerZeroFault
ai agents-api

ElizaOS SKILL.md Frontmatter Parse Failure on CRLF Line Endings

VV

Written by

Published:

Technical Guide

To resolve ElizaOS SKILL.md Frontmatter Parse Failure on CRLF Line Endings, resolve ElizaOS SKILL.md frontmatter parsing errors on Windows and CRLF checkouts. Fix loadSkillFromStorage null returns, MISSING_METADATA failures, and parser regex bugs. Ensure all derivation paths, environment parameters, and cryptographic signatures are verified client-side using deterministic tools before broadcasting live transactions.

When developing autonomous AI agents with ElizaOS (formerly ai16z/eliza) using modular skill definitions (plugin-agent-skills), developers often encounter a baffling failure mode: custom skills placed inside the skills directory fail to load, loadSkillFromStorage silently returns null, or the runtime validator outputs MISSING_METADATA: SKILL.md frontmatter could not be parsed.

This issue occurs almost exclusively on Windows development environments, WSL mounts, or CI/CD pipelines where Git checkouts have converted line endings from standard Unix line feeds (LF, \n) to Windows carriage returns (CRLF, \r\n).

If your agent is also experiencing conversational schema breakdown where actions fail to fire, check our companion guide on Fixing ElizaOS Action Triggers Returning AI Model Responses.


Technical Root Cause Analysis: The LF-Only Regex Gate

In the official ElizaOS Agent Skills plugin (plugins/plugin-agent-skills/src/parser.ts), frontmatter extraction is implemented through a hardcoded regular expression:

The Vulnerable Source Code

// Location: plugins/plugin-agent-skills/src/parser.ts
export function parseFrontmatter(content: string): { 
  frontmatter: SkillMetadata | null; 
  body: string 
} {
  // ❌ VULNERABLE: LF-only line ending anchor (^---\n)
  const match = content.match(/^---\n([\s\S]*?)\n---\n?/);
  
  if (!match) {
    // Silently fails on any file authored with CRLF (\r\n) line breaks
    return { frontmatter: null, body: content };
  }

  try {
    const rawYaml = match[1];
    const frontmatter = yaml.load(rawYaml) as SkillMetadata;
    const body = content.slice(match[0].length).trim();
    return { frontmatter, body };
  } catch (error) {
    return { frontmatter: null, body: content };
  }
}

The Failure Cascade

  1. CRLF Encoding Injection: When a file is created on Windows (Notepad, VS Code default Windows settings) or cloned via Git with core.autocrlf = true, the text starts with the byte sequence 2D 2D 2D 0D 0A (---\r\n).
  2. Regex Mismatch: The regular expression /^---\n/ strictly expects the byte sequence 2D 2D 2D 0A (---\n). Because \r precedes \n, the regex pattern evaluation immediately yields null.
  3. Silent Downstream Degradation:
    • loadSkillFromStorage() receives { frontmatter: null } and aborts skill hydration.
    • validateSkillDirectory() throws a fatal MISSING_METADATA error.
    • The ElizaOS agent boots up without throwing a crash stack trace, but none of the skill’s actions, memory providers, or evaluators are registered in runtime.actions.

Permanent Remediation Protocol

To completely eliminate SKILL.md parsing errors across all operating systems and deployment pipelines, apply the following three-tier fix.

Step 1: Patch parser.ts with CRLF-Agnostic Regex

Modify the parser regex in plugins/plugin-agent-skills/src/parser.ts to explicitly permit optional \r carriage returns across all frontmatter delimiters:

// Location: plugins/plugin-agent-skills/src/parser.ts
import * as yaml from 'js-yaml';

export interface SkillMetadata {
  name: string;
  description: string;
  version?: string;
  author?: string;
  tags?: string[];
  [key: string]: any;
}

// ✅ ROBUST REGEX: Supports both LF (\n) and CRLF (\r\n) boundaries
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;

export function parseFrontmatter(content: string): { 
  frontmatter: SkillMetadata | null; 
  body: string 
} {
  if (!content || typeof content !== 'string') {
    return { frontmatter: null, body: '' };
  }

  // Pre-normalize or match directly with cross-platform regex
  const match = content.match(FRONTMATTER_REGEX);
  
  if (!match) {
    console.warn('[plugin-agent-skills] Warning: Failed to extract YAML frontmatter header.');
    return { frontmatter: null, body: content };
  }

  try {
    const rawYaml = match[1];
    const frontmatter = yaml.load(rawYaml) as SkillMetadata;
    const body = content.slice(match[0].length).trim();
    
    return { frontmatter, body };
  } catch (error) {
    console.error('[plugin-agent-skills] YAML parsing exception in SKILL.md:', error);
    return { frontmatter: null, body: content };
  }
}

Step 2: Enforce Repository-Level .gitattributes

Ensure that Git never converts Markdown or configuration files to Windows CRLF during checkout on developer machines or Windows CI runners:

Create or update .gitattributes in your repository root:

# Ensure all markdown, skill manifests, and JSON stay LF across all OS checkouts
*.md text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.js text eol=lf
SKILL.md text eol=lf

After updating .gitattributes, normalize existing files in your repository:

git add --renormalize .
git commit -m "fix(skills): normalize repository line endings to LF"

Step 3: Configure VS Code Workspace Settings

Add .vscode/settings.json to prevent your local editor from saving new skill templates with Windows CRLF:

{
  "files.eol": "\n",
  "[markdown]": {
    "files.eol": "\n",
    "editor.trimAutoWhitespace": true
  }
}

Batch Line Ending Conversion Script

If you have dozens of existing skill directories failing validation, run this Node.js utility script to convert all SKILL.md files in your workspace to clean LF formatting:

// scripts/sanitize-skills.js
const fs = require('fs');
const path = require('path');

function sanitizeSkillFiles(dir) {
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      sanitizeSkillFiles(fullPath);
    } else if (entry.name.toLowerCase() === 'skill.md') {
      const original = fs.readFileSync(fullPath, 'utf8');
      const normalized = original.replace(/\r\n/g, '\n');
      if (original !== normalized) {
        fs.writeFileSync(fullPath, normalized, 'utf8');
        console.log(`[+] Normalized CRLF -> LF in: ${fullPath}`);
      }
    }
  }
}

const skillsRoot = path.resolve(__dirname, '../skills');
if (fs.existsSync(skillsRoot)) {
  sanitizeSkillFiles(skillsRoot);
  console.log('Skill directory sanitization complete.');
} else {
  console.error(`Skills directory not found at: ${skillsRoot}`);
}

Execute via terminal:

node scripts/sanitize-skills.js

Diagnostic Matrix: Frontmatter Parsing Failures

Symptom / Log OutputUnderlying BugSolution
MISSING_METADATA: SKILL.md frontmatter could not be parsedWindows \r\n line breaks in YAML frontmatter delimitersPatch regex to /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/
loadSkillFromStorage returns nullFrontmatter regex returned null, triggering silent guard exitSanitize file with dos2unix SKILL.md
Agent actions not registering on runtime bootSkill metadata failed validation during startup sequenceVerify YAML syntax with js-yaml validator
YAMLException: bad indentation of a mapping entryTabs instead of spaces in YAML frontmatter blockReplace tabs with 2 spaces in YAML keys

[!TIP] Diagnostic Verification & Air-Gapped Testing: Before executing state changes or broad recovery steps, cross-reference failure parameters with our companion guide on ElizaOS Solana Agent Kit Public Key Not Found and verify transaction calldata or cryptographic envelopes using the Master Web3 & RPC Error Code Directory.

Frequently Asked Questions

Q: Why does the skill load successfully on Linux production servers but fail on my Windows local machine?

Linux git checkouts retain standard Unix LF (\n) line feeds, which perfectly match the original regex. Windows development environments default to CRLF (\r\n), causing the exact same repository to fail locally while running fine in Docker/Linux.

Q: Can I use standard npm packages like gray-matter instead of regex?

Yes! Replacing custom regex extraction with robust libraries like gray-matter or yaml-front-matter handles cross-platform delimiters, UTF-8 BOM headers, and multi-line scalar values automatically without manual regex patching.

Q: What is the minimal valid SKILL.md frontmatter structure in ElizaOS?

Every SKILL.md must include at least a name and description:

---
name: "defi-swap-skill"
description: "Executes decentralized token swaps across EVM DEX protocols"
version: "1.0.0"
---

# Skill Instructions and Action Implementations
Interactive Diagnostic Sandbox

Local Agent API & Calldata Sandboxes

Inspect autonomous agent UserOperations, decode packed execution calldata, and test programmatic authentication payloads locally.

Companion Diagnostic Guide ElizaOS Action Triggers Return AI Model Responses →

Resolve ElizaOS action triggers returning raw LLM responses instead of handler callbacks. Fix runtime schema validation breaks and enforce handler routing.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

Why does ElizaOS fail to parse SKILL.md files on Windows systems?

In ElizaOS plugin-agent-skills, the parseFrontmatter function extracts YAML headers using an LF-only regular expression. On Windows or Git checkouts configured with CRLF, the initial delimiter starts with CRLF bytes, which fails to match the anchor and causes the parser to return frontmatter null.

What error appears when SKILL.md fails to load?

The storage loader loadSkillFromStorage silently returns null, causing validateSkillDirectory to throw a spurious 'MISSING_METADATA: SKILL.md frontmatter could not be parsed' exception or causing custom agent tools and actions to silently vanish from runtime execution.

How do I quickly fix the CRLF frontmatter parsing bug in my ElizaOS agent?

Patch the parser regex in plugins/plugin-agent-skills/src/parser.ts to accept optional carriage returns or configure .gitattributes with '*.md text eol=lf' to force standard LF line endings.

Does this bug affect runtime action execution or character prompts?

Yes. When skills fail to load, the agent cannot register skill-associated actions, fallback handlers, or tool schemas, frequently causing the agent to output raw text or unformatted chat responses instead of executing automated transactions.