abhigyanpatwari/GitNexus · critical · ValueError
candidate overlays may only contain Markdown files under .cl
Error message
candidate overlays may only contain Markdown files under .claude/skills/gitnexus-{plan,work}: {relative} What it means
Thrown by candidate_overlay_files (evolution.py:326) when any collected file fails the trust-boundary shape test: its relative path must have at least 4 parts, parts[:2] == ('.claude','skills'), parts[2] in CANDIDATE_SKILLS ({'gitnexus-plan','gitnexus-work'}), and suffix.lower() == '.md'. This enforces that candidates may only modify the canonical plan/work skill prompts — they cannot touch task code, tests, or other skills and thereby game the promotion gate.
Source
Thrown at eval/workflow_bench/evolution.py:326
entries.append(path)
if len(entries) > MAX_CANDIDATE_FILES:
raise ValueError(f"candidate overlay exceeds the {MAX_CANDIDATE_FILES}-file limit")
pending.extend(child_directories)
entries.sort(key=lambda path: path.relative_to(overlay).as_posix())
if not entries:
raise ValueError(f"candidate overlay contains no files: {overlay}")
for path in entries:
relative = path.relative_to(overlay)
parts = relative.parts
if (
len(parts) < 4
or parts[:2] != (".claude", "skills")
or parts[2] not in CANDIDATE_SKILLS
or path.suffix.lower() != ".md"
):
raise ValueError(
"candidate overlays may only contain Markdown files under "
".claude/skills/gitnexus-{plan,work}: "
f"{relative}"
)
return entries
def required_candidate_arms(overlay: Path) -> list[str]:
"""Return the smallest candidate-arm set that exercises every change.
Plan prompts are loaded only by the two-session workflow. Work prompts are
loaded by both workflow shapes, so a work candidate must prove itself in
both rather than inheriting a decision from an untested execution mode.
"""
overlay = overlay.expanduser().absolute()
touched = {path.relative_to(overlay).parts[2] for path in candidate_overlay_files(overlay)}
required: list[str] = []
if "gitnexus-plan" in touched or "gitnexus-work" in touched:View on GitHub (pinned to d540b00184)
Solutions
- Move every overlay file under .claude/skills/gitnexus-plan/ or .claude/skills/gitnexus-work/ with a .md extension.
- Remove any file that is not a plan/work skill Markdown prompt.
- Validate each path's parts and suffix before invoking the harness.
Example fix
# before: overlay contains a review-skill file and a .txt
.claude/skills/gitnexus-review/SKILL.md
notes.txt
# after: only plan/work .md files
from pathlib import PurePosixPath
ALLOWED = {'gitnexus-plan', 'gitnexus-work'}
for p in Path('overlay').rglob('*'):
if p.is_file():
rel = PurePosixPath(p.relative_to('overlay').as_posix())
ok = (len(rel.parts) >= 4 and rel.parts[:2] == ('.claude','skills')
and rel.parts[2] in ALLOWED and rel.suffix.lower() == '.md')
assert ok, f'outside trust boundary: {rel}' Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path, PurePosixPath
from workflow_bench.evolution import CANDIDATE_SKILLS
def overlay_within_trust_boundary(root: Path) -> bool:
for p in Path(root).rglob('*'):
if not p.is_file():
continue
rel = PurePosixPath(p.relative_to(root).as_posix())
if (
len(rel.parts) < 4
or rel.parts[:2] != ('.claude', 'skills')
or rel.parts[2] not in CANDIDATE_SKILLS
or rel.suffix.lower() != '.md'
):
return False
return True Type guard
from pathlib import PurePosixPath
from workflow_bench.evolution import CANDIDATE_SKILLS
def is_allowed_skill_path(rel: PurePosixPath) -> bool:
return (
len(rel.parts) >= 4
and rel.parts[:2] == ('.claude', 'skills')
and rel.parts[2] in CANDIDATE_SKILLS
and rel.suffix.lower() == '.md'
) Try / catch
try:
apply_candidate_overlay(overlay, worktree, sandbox=sandbox)
except ValueError as exc:
if 'may only contain Markdown files under' in str(exc):
# move/rename the offending file under .claude/skills/gitnexus-{plan,work}/, then retry
... Prevention
- Only place files under .claude/skills/gitnexus-plan/ or gitnexus-work/.
- Use .md extensions exclusively for overlay content.
- Validate every path's parts and suffix against CANDIDATE_SKILLS before running.
When it happens
Trigger: An overlay file lives outside .claude/skills/gitnexus-plan/ or .claude/skills/gitnexus-work/; a file under a non-candidate skill (e.g. gitnexus-review); a non-.md file; or a path too shallow (<4 parts) such as a top-level file.
Common situations: Trying to update a review/debugging skill via the overlay; including a .txt/.json helper; placing a file directly under .claude/ instead of .claude/skills/gitnexus-work/.
Related errors
- candidate destination escapes the clone: {relative}
- candidate destination must be a regular non-symlink file: {r
- candidate overlay cannot traverse symlinks: {overlay}
- candidate overlay cannot contain symlinks: {relative}
- candidate overlay entries must be regular files: {relative}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/d72b5feaa01d033e.
Report an issue: GitHub.