opendataloader-project/opendataloader-pdf · error · ConfigError
no SKILL.md found under {skill_dir} (nothing to lint)
Error message
no SKILL.md found under {skill_dir} (nothing to lint) What it means
ConfigError (exit code 2) raised by _scanned_files when the --skill-dir does not contain a SKILL.md file. The lint is designed to scan agent-facing prose (SKILL.md + references/*.md); without SKILL.md the directory is not a skill bundle, so scanning 0 files and reporting PASS would be a false negative. This input/config error is deliberately separated (exit 2) from a real coupling violation (exit 1).
Source
Thrown at skills/odl-pdf-maintenance/sync-skill-refs.py:91
_FLAG_RE = re.compile(r"(?<![A-Za-z0-9])--[a-z0-9][a-z0-9-]*")
# A run of dot-separated integers (semver OR IPv4); classified after matching.
_DOTTED_RE = re.compile(r"\d+(?:\.\d+)+")
# Referenced bundle paths the prose points at (must exist under the skill dir).
_PATH_RE = re.compile(r"\b((?:references|scripts)/[A-Za-z0-9_.-]+\.(?:md|py|sh))\b")
class ConfigError(Exception):
"""Bad input/config (missing SKILL.md, unreadable file). Reported as exit 2,
distinct from exit 1 (a coupling/structural violation in the authored prose)."""
def _scanned_files(skill_dir: Path):
"""Agent-facing prose: SKILL.md + references/*.md. ConfigError if the dir does
not look like a skill bundle (no SKILL.md) — an empty/wrong --skill-dir must not
silently scan 0 files and report a false PASS."""
skill_md = skill_dir / "SKILL.md"
if not skill_md.is_file():
raise ConfigError(f"no SKILL.md found under {skill_dir} (nothing to lint)")
files = [skill_md] + sorted((skill_dir / "references").glob("*.md"))
return [f for f in files if f.is_file()]
def _read(f: Path) -> str:
try:
return f.read_text(encoding="utf-8")
except OSError as e:
raise ConfigError(f"{f}: {e}") from e
def _is_ipv4(run: str) -> bool:
"""A dotted run that is a valid dotted-quad IPv4 (4 octets, each 0-255)."""
parts = run.split(".")
return len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts)
def check_versions(rel: str, text: str, violations: list):View on GitHub (pinned to a7789b8e77)
Solutions
- Point --skill-dir at the actual skill bundle directory containing SKILL.md (by default the script's sibling odl-pdf dir).
- Verify with ls <skill-dir>/SKILL.md before running.
- If the bundle is generated by a build step, ensure that step ran before the lint.
- Distinguish exit code 2 (this config error) from exit 1 (a real violation) in CI scripting.
Example fix
# before: wrong dir (the script's own dir, no SKILL.md) $ ./sync-skill-refs.py --skill-dir skills/odl-pdf-maintenance # -> ConfigError # after: the distributed skill bundle $ ./sync-skill-refs.py --skill-dir skills/odl-pdf
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def is_skill_bundle(skill_dir: str) -> bool:
return (Path(skill_dir) / "SKILL.md").is_file()
Type guard
def is_no_skill_md_error(exc: Exception) -> bool:
return exc.__class__.__name__ == "ConfigError" and "no SKILL.md found under" in str(exc) Try / catch
import subprocess, sys
rc = subprocess.run([sys.executable, "sync-skill-refs.py", "--skill-dir", d]).returncode
if rc == 2:
print("Config error (exit 2): bad --skill-dir; point at the dir containing SKILL.md")
elif rc == 1:
print("Lint violation (exit 1): fix the coupling issue in the prose") Prevention
- Point --skill-dir at the bundle dir that contains SKILL.md (the sibling odl-pdf dir by default).
- Distinguish exit code 2 (config) from exit 1 (real violation) in CI.
- Verify ls <dir>/SKILL.md before running the lint.
When it happens
Trigger: Running sync-skill-refs.py with a --skill-dir that points at the wrong directory (e.g. the repo root, the maintenance dir instead of the odl-pdf bundle dir, or a path where the skill has not been assembled).
Common situations: CI invokes the lint with a path that changed after a directory rename. A developer passes the maintenance dir (which contains the script, not SKILL.md). The skill bundle has not been generated/copied yet. A typo in the path.
Related errors
- {f}: {e}
- Options --hybrid-hancom-ai-* require --hybrid=hancom-ai (got
- Unsupported hybrid backend: {}
- Input file not found: {input_path}
- Unsupported format: {format!r}. Supported formats: {', '.joi
AI-assisted analysis of opendataloader-project/opendataloader-pdf@a7789b8e77 (2026-08-14).
Data as JSON: /api/errors/4fe3c5ad4b5206b7.
Report an issue: GitHub.