Simplify skill frontmatter warnings
Keep the malformed SKILL.md warning behavior, but reduce the diagnostic machinery to a once-per-path warning with line numbers only for parser-owned structural errors. Update the regression to assert the stable warning message without depending on the previous path/line/error dedupe key.
Alessandro committed
Jun 25, 2026 at 21:48 UTC
bf72345da64556e89d4a77cc25e769958f5b3e2e
3 files changed
+16
-25
helpers/skills.py
+10
-20
@@ -25,7 +25,7 @@ CONTEXT_DATA_NAME_LOADED_SKILLS = AGENT_DATA_NAME_LOADED_SKILLS
25
CONTEXT_DATA_NAME_CHAT_ACTIVE_SKILLS = "skills_chat_active"
26
CONTEXT_DATA_NAME_CHAT_DISABLED_SKILLS = "skills_chat_disabled"
27
CONTEXT_DATA_NAME_CHAT_VISIBLE_SKILLS = "skills_chat_visible"
28
-_SKILL_PARSE_WARNINGS: set[str] = set()
28
+_WARNED_SKILL_PARSE_PATHS: set[Path] = set()
29
30
31
class ActiveSkillEntry(TypedDict, total=False):
@@ -264,9 +264,7 @@ def _emit_skill_scan_warning(message: str) -> None:
264
print(f"Warning: {message}")
265
266
267
-def _frontmatter_error_line(markdown: str, error: str) -> int:
268
- text = markdown or ""
269
- lines = text.splitlines()
267
+def _frontmatter_error_line(lines: List[str], error: str) -> int | None:
268
if not lines:
269
return 1
270
@@ -279,30 +277,22 @@ def _frontmatter_error_line(markdown: str, error: str) -> int:
277
return 1
278
if error.startswith("Unterminated YAML frontmatter"):
279
return max(len(lines), 1)
282
-
283
- match = re.search(r"line\s+(\d+)", error, flags=re.IGNORECASE)
284
- if match:
285
- start_idx = 0
286
- for index, line in enumerate(lines):
287
- if line.strip() == "---":
288
- start_idx = index
289
- break
290
- return start_idx + int(match.group(1)) + 1
291
- return 1
280
+ return None
281
282
283
def _warn_skill_skipped(skill_md_path: Path, markdown: str, errors: List[str]) -> None:
284
if not errors:
285
return
297
- error = str(errors[0] or "invalid frontmatter").strip()
298
- line = _frontmatter_error_line(markdown, error)
299
- key = f"{skill_md_path}:{line}:{error}"
300
- if key in _SKILL_PARSE_WARNINGS:
286
+ if skill_md_path in _WARNED_SKILL_PARSE_PATHS:
287
return
302
- _SKILL_PARSE_WARNINGS.add(key)
288
+ _WARNED_SKILL_PARSE_PATHS.add(skill_md_path)
289
+
290
+ error = str(errors[0] or "invalid frontmatter").strip()
291
+ line = _frontmatter_error_line((markdown or "").splitlines(), error)
292
skill_label = skill_md_path.parent.name or str(skill_md_path)
293
+ location = f" at line {line}" if line is not None else ""
294
_emit_skill_scan_warning(
305
- f"skill {skill_label} skipped: invalid frontmatter at line {line}: {error}"
295
+ f"skill {skill_label} skipped: invalid frontmatter{location}: {error}"
296
)
297
298
helpers/skills.py.dox.md
+1
-1
@@ -56,7 +56,7 @@
56
- Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
57
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
58
- Loaded skill names are chat-wide context data under `CONTEXT_DATA_NAME_LOADED_SKILLS`; legacy agent-local `loaded_skills` lists are migrated into context data and cleared when read.
59
-- Invalid `SKILL.md` frontmatter emits a deduplicated scan warning with the skipped skill path/name and best-effort line number instead of disappearing silently from lists, search, catalog, and load.
59
+- Invalid `SKILL.md` frontmatter emits a once-per-path scan warning with the skipped skill path/name and a line number when the parser can identify one directly.
60
- Observed side-effect areas: filesystem reads, filesystem deletion, plugin state, settings/state persistence, context data, secret handling.
61
- Imported dependency areas include: `__future__`, `dataclasses`, `helpers`, `os`, `pathlib`, `re`, `typing`.
62
tests/test_skills_runtime.py
+5
-4
@@ -341,14 +341,15 @@ def test_invalid_skill_frontmatter_warns_when_skill_is_skipped(monkeypatch, tmp_
341
)
342
343
warnings: list[str] = []
344
- runtime._SKILL_PARSE_WARNINGS.clear()
344
+ runtime._WARNED_SKILL_PARSE_PATHS.clear()
345
monkeypatch.setattr(runtime, "get_skill_roots", lambda agent=None: [str(skills_root)])
346
monkeypatch.setattr(runtime, "_emit_skill_scan_warning", warnings.append)
347
348
assert runtime.list_skills() == []
349
- assert len(warnings) == 1
350
- assert "skill broken-skill skipped: invalid frontmatter at line 4" in warnings[0]
351
- assert "Unterminated YAML frontmatter" in warnings[0]
349
+ assert warnings == [
350
+ "skill broken-skill skipped: invalid frontmatter at line 4: "
351
+ "Unterminated YAML frontmatter"
352
+ ]
353
354
assert runtime.list_skills() == []
355
assert len(warnings) == 1