Skip to content

fix(presets): treat non-mapping command frontmatter as empty - #5125

Open
xubenqiang wants to merge 2 commits into
github:mainfrom
xubenqiang:fix/preset-non-mapping-command-frontmatter
Open

xubenqiang wants to merge 2 commits into
github:mainfrom
xubenqiang:fix/preset-non-mapping-command-frontmatter

Conversation

@xubenqiang

Copy link
Copy Markdown

Description

PresetResolver.resolve_content parses the winning command layer's frontmatter with _parse_fm_yaml, which returned whatever yaml.safe_load produced. A composing command (append / prepend / wrap) whose frontmatter is valid YAML but not a mapping therefore reached top_fm.pop("strategy", None) as a str and preset add surfaced a raw AttributeError: 'str' object has no attribute 'pop' instead of a preset error.

CommandRegistrar.parse_frontmatter already treats a non-dict result as {}, and this resolver already does the same for yaml.YAMLError. _parse_fm_yaml now applies the same guard, so non-mapping frontmatter is treated as empty and composition continues.

Fixes #5085

Testing

  • Ran existing tests: pytest tests/specify_cli/presets/test_resolver.py -q -> 74 passed, 1 failed
  • Tested locally with uv run specify --help
  • Tested with a sample project (if applicable)

The one failure is TestPresetResolver::test_resolve_fails_closed_when_registry_is_broken_symlink, which fails the same way on an unmodified checkout on this Windows machine (OSError: [WinError 1314], symlink creation needs a privilege this shell does not hold), so it is environmental, not caused by this change.

The new test installs a preset whose command layer has ---\njust a note\n--- frontmatter over a core command. With the fix it resolves and contains the appended body without the invalid frontmatter; with _resolver.py reverted it fails with AttributeError: 'str' object has no attribute 'pop'.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: DeepSeek Harness coding agent (model deepseek-flash), running autonomously under a human-directed task. Extent: read the referenced code, wrote the isinstance guard in _resolver.py, the regression test, and ran the suite locally; the human operator reviewed the submitted diff.

_parse_fm_yaml returned whatever yaml.safe_load produced, and resolve_content
then called .pop on it, so a composing command whose frontmatter was valid YAML
but not a mapping crashed preset add with AttributeError. Fall back to {} like
CommandRegistrar.parse_frontmatter already does.
@xubenqiang
xubenqiang requested a review from mnriem as a code owner October 26, 2026 12:53
Copilot AI max review requested due to automatic review settings October 26, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@mnriem mnriem added the triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate label Oct 26, 2026
@mnriem
mnriem requested a max review from Copilot October 26, 2026 14:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The localized guard preserves mapping behavior, has targeted regression coverage, and presents no identified blocking issues.

1 open issue

Review effort: Max

Comment thread tests/specify_cli/presets/test_resolver.py
@mnriem

mnriem commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

Please address Copilot feedback

@mnriem mnriem added the author-awaiting Waiting on author response label Oct 26, 2026
Addresses the Copilot review note: the test would have passed even if
non-mapping frontmatter had dropped the base layer, so assert its body is
still in the resolved content.
@xubenqiang

Copy link
Copy Markdown
Author

Added the assertion Copilot suggested in 0b25a4e: �ssert "CORE BODY" in content, so the test now fails if the base command is dropped during composition.

Copilot AI max review requested due to automatic review settings October 27, 2026 03:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@mnriem
mnriem requested a max review from Copilot October 27, 2026 19:19
@mnriem mnriem removed the author-awaiting Waiting on author response label Oct 27, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The narrow guard preserves mapping behavior and has targeted regression coverage for the reported crash.

No open issues 🎉

1 resolved since last review

Review effort: Max

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: preset add crashes with AttributeError when a composing command's frontmatter is not a YAML mapping

3 participants