LayerZeroFault
ai agents-api

Fix: ElizaOS SKILL.md Frontmatter Parse Failure on CRLF Line Endings

VV

Written by

Fact-Checked on August 19, 2026

Verified Expert

Fix: ElizaOS SKILL.md Frontmatter Parse Failure on CRLF Line Endings

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:

Placeholder: Architectural diagram of ElizaOS SKILL.md Parser regex match failure on CRLF tokens

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.

Placeholder: Flowchart of robust multi-platform YAML frontmatter parser and line-break sanitizer

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

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
Partner Spotlight: Gate.io

Trade Securely on Gate.io

Don't risk your assets on centralized silos or unverified endpoints. Trade securely on Gate.io with deep liquidity and institutional-grade security protocols.

Claim $100 Sign-up Bonus

Official Partner Referral Link

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 (/^---\n([\s\S]*?)\n---\n?/). On Windows or Git checkouts configured with CRLF (\r\n), the initial delimiter starts with '---\r\n', 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 (/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/) or configure .gitattributes with '*.md text eol=lf' to force 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.