[BUG] Duplicate YAML keys in agent frontmatter cause silent parse failure, agent loads with mode: "all" instead of configured mode #8587

Open
opened 2026-02-16 18:10:20 -05:00 by yindo · 2 comments
Owner

Originally created by @benceferdinandy-signifyd on GitHub (Feb 5, 2026).

Originally assigned to: @thdxr on GitHub.

Description

(I also manually verified by checking out the repo before the offending commit and seeing if this still happens or not, the rest of the analysis is Opus 4.5)

When an agent markdown file contains duplicate YAML keys in its frontmatter (e.g., pwd: allow appearing twice in the bash permission section), the YAML parsing silently fails and returns empty data {}. This causes the agent to load with default values, including mode: "all" instead of the configured mode: "subagent".

Regression introduced in: Commit 33c5c100f (Jan 28, 2026) - "fix: frontmatter was adding newlines in some cases causing invalid model ids (#11095)"

Consequence: Subagents incorrectly appear in the primary agent tab cycle (Tab key), even though they are explicitly configured as mode: subagent.

Root cause analysis:

  1. gray-matter attempts to parse the frontmatter and throws YAMLException: duplicated mapping key
  2. ConfigMarkdown.parse() catches this and tries fallbackSanitization(), which doesn't handle duplicate keys
  3. Due to what appears to be a caching/state bug in gray-matter or js-yaml, the second matter() call succeeds but returns {} instead of throwing
  4. The agent config merges with empty data, defaulting mode to "all" (line 213 in agent.ts)
  5. Agents with mode: "all" or mode: "primary" appear in the tab cycle

Debug evidence:

// First call fails as expected
matter(content)  // throws: YAMLException duplicated mapping key

// Second call (after first failure) silently succeeds with empty data!
matter(content)  // returns { data: {}, content: "..." }

This inconsistent behavior between consecutive calls to matter() with the same content suggests internal state corruption in gray-matter/js-yaml after an error.

Example problematic frontmatter (simplified):

---
mode: subagent
permission:
  bash:
    pwd: allow    # line 57
    # ... other entries ...
    pwd: allow    # line 68 - DUPLICATE KEY!
---

This commit changed ConfigMarkdown.parse() from always preprocessing with preprocessFrontmatter() to a try/catch fallback pattern. The previous behavior (commit dc1c25cff) would properly throw an error on invalid YAML. The new behavior silently returns empty data due to the gray-matter state corruption bug.

Related issues:

  • #8868 - Agents and Commands intermittently not shown (likely same root cause)
  • #7022 - SKILL files YAML parsing failures
  • #3537 - Custom slash commands crash on startup (YAML issues)
  • #11095 - The PR that introduced this regression

Plugins

@franlol/opencode-md-table-formatter@0.0.3


OpenCode version

Built from source (dev branch)


Steps to reproduce

  1. Create an agent file with mode: subagent in frontmatter
  2. Add a duplicate key somewhere in the YAML frontmatter (e.g., pwd: allow twice in bash permissions)
  3. Start opencode
  4. Press Tab to cycle through agents
  5. Expected: Only primary agents (build, plan) appear in cycle
  6. Actual: The subagent also appears in the tab cycle

Reproduction config:


Screenshot and/or share link

N/A


Operating System

macOS (darwin arm64)


Terminal

N/A (reproduced via code analysis)


Suggested fixes

  1. Immediate: Warn users when agent frontmatter parsing returns empty data unexpectedly (compare input size vs parsed data)
  2. Better: Use a YAML parser that doesn't have this caching issue, or reset parser state between calls
  3. Best: Improve fallbackSanitization to detect and handle duplicate keys, or validate YAML before passing to gray-matter
  4. Alternative: Consider reverting to the pre-33c5c100f behavior where preprocessFrontmatter was always applied first, not as a fallback. This avoids triggering the gray-matter state corruption entirely.
Originally created by @benceferdinandy-signifyd on GitHub (Feb 5, 2026). Originally assigned to: @thdxr on GitHub. ### Description (I also manually verified by checking out the repo before the offending commit and seeing if this still happens or not, the rest of the analysis is Opus 4.5) When an agent markdown file contains duplicate YAML keys in its frontmatter (e.g., `pwd: allow` appearing twice in the bash permission section), the YAML parsing silently fails and returns empty data `{}`. This causes the agent to load with default values, including `mode: "all"` instead of the configured `mode: "subagent"`. **Regression introduced in:** Commit `33c5c100f` (Jan 28, 2026) - "fix: frontmatter was adding newlines in some cases causing invalid model ids (#11095)" **Consequence:** Subagents incorrectly appear in the primary agent tab cycle (Tab key), even though they are explicitly configured as `mode: subagent`. **Root cause analysis:** 1. `gray-matter` attempts to parse the frontmatter and throws `YAMLException: duplicated mapping key` 2. `ConfigMarkdown.parse()` catches this and tries `fallbackSanitization()`, which doesn't handle duplicate keys 3. Due to what appears to be a caching/state bug in gray-matter or js-yaml, the second `matter()` call **succeeds but returns `{}`** instead of throwing 4. The agent config merges with empty data, defaulting `mode` to `"all"` (line 213 in `agent.ts`) 5. Agents with `mode: "all"` or `mode: "primary"` appear in the tab cycle **Debug evidence:** ```javascript // First call fails as expected matter(content) // throws: YAMLException duplicated mapping key // Second call (after first failure) silently succeeds with empty data! matter(content) // returns { data: {}, content: "..." } ``` This inconsistent behavior between consecutive calls to `matter()` with the same content suggests internal state corruption in gray-matter/js-yaml after an error. **Example problematic frontmatter** (simplified): ```yaml --- mode: subagent permission: bash: pwd: allow # line 57 # ... other entries ... pwd: allow # line 68 - DUPLICATE KEY! --- ``` This commit changed `ConfigMarkdown.parse()` from always preprocessing with `preprocessFrontmatter()` to a try/catch fallback pattern. The previous behavior (commit `dc1c25cff`) would properly throw an error on invalid YAML. The new behavior silently returns empty data due to the gray-matter state corruption bug. **Related issues:** - #8868 - Agents and Commands intermittently not shown (likely same root cause) - #7022 - SKILL files YAML parsing failures - #3537 - Custom slash commands crash on startup (YAML issues) - #11095 - The PR that introduced this regression --- ### Plugins `@franlol/opencode-md-table-formatter@0.0.3` --- ### OpenCode version Built from source (dev branch) --- ### Steps to reproduce 1. Create an agent file with `mode: subagent` in frontmatter 2. Add a duplicate key somewhere in the YAML frontmatter (e.g., `pwd: allow` twice in bash permissions) 3. Start opencode 4. Press Tab to cycle through agents 5. **Expected:** Only primary agents (build, plan) appear in cycle 6. **Actual:** The subagent also appears in the tab cycle **Reproduction config:** - Agent files with duplicate keys: https://github.com/ferdinandyb/dotfiles/blob/b7eb33f86a53df4e69f9d09ca1f3de1c8d3b2189/.config/opencode/agent/code-reviewer.md (lines 57 and 68 both have `pwd: allow`) --- ### Screenshot and/or share link N/A --- ### Operating System macOS (darwin arm64) --- ### Terminal N/A (reproduced via code analysis) --- ### Suggested fixes 1. **Immediate:** Warn users when agent frontmatter parsing returns empty data unexpectedly (compare input size vs parsed data) 2. **Better:** Use a YAML parser that doesn't have this caching issue, or reset parser state between calls 3. **Best:** Improve `fallbackSanitization` to detect and handle duplicate keys, or validate YAML before passing to gray-matter 4. **Alternative:** Consider reverting to the pre-`33c5c100f` behavior where `preprocessFrontmatter` was always applied first, not as a fallback. This avoids triggering the gray-matter state corruption entirely.
Author
Owner

@github-actions[bot] commented on GitHub (Feb 5, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #8868: Agents and Commands are not shown - relates to agents/commands failing to load due to parsing issues
  • #7022: opencode scans claude code's SKILL files and sometimes failed to parse YAML - YAML parsing failures in config files
  • #3537: Custom slash commands can crash opencode on startup - YAML parsing crashes with invalid frontmatter syntax
  • #8733: Subagent not reading markdown instructions from agent definition file - subagent configuration not properly loaded from markdown

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Feb 5, 2026): This issue might be a duplicate of existing issues. Please check: - #8868: Agents and Commands are not shown - relates to agents/commands failing to load due to parsing issues - #7022: opencode scans claude code's SKILL files and sometimes failed to parse YAML - YAML parsing failures in config files - #3537: Custom slash commands can crash opencode on startup - YAML parsing crashes with invalid frontmatter syntax - #8733: Subagent not reading markdown instructions from agent definition file - subagent configuration not properly loaded from markdown Feel free to ignore if none of these address your specific case.
Author
Owner

@benceferdinandy-signifyd commented on GitHub (Feb 5, 2026):

After reviewing #8868, I believe this is the same root cause. In #8868, the user reports that agent: {} appears intermittently - sometimes agents load, sometimes they don't. This matches the gray-matter caching behavior I identified: the first call to matter() fails, and subsequent calls return empty {}.

The intermittent nature in #8868 likely depends on whether another agent file with invalid YAML was parsed first, corrupting the gray-matter internal state.

@benceferdinandy-signifyd commented on GitHub (Feb 5, 2026): After reviewing #8868, I believe this is the same root cause. In #8868, the user reports that `agent: {}` appears intermittently - sometimes agents load, sometimes they don't. This matches the gray-matter caching behavior I identified: the first call to `matter()` fails, and subsequent calls return empty `{}`. The intermittent nature in #8868 likely depends on whether another agent file with invalid YAML was parsed first, corrupting the gray-matter internal state.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8587