abhigyanpatwari/GitNexus · error · SandboxError
Compound Engineering plugin manifest directory must be real:
Error message
Compound Engineering plugin manifest directory must be real: {manifest_root} What it means
Even when .claude-plugin/ can be lstat'd, the harness rejects it if the mode is a symlink (stat.S_ISLNK) or not a directory (not stat.S_ISDIR). Real directories are required so a symlink cannot redirect manifest reads outside the bounded source.
Source
Thrown at eval/workflow_bench/runtime_mounts.py:298
if lowered.startswith(".env.") or lowered.startswith(".npmrc."):
return True
if lowered.endswith(_SECRET_SUFFIXES) or any(marker in lowered for marker in _SECRET_NAME_MARKERS):
return True
return False
def _plugin_files(source: Path) -> Iterator[tuple[PurePosixPath, Path]]:
"""Yield only allowlisted plugin files in stable order."""
manifest_root = source / ".claude-plugin"
try:
manifest_root_metadata = manifest_root.lstat()
except OSError as exc:
raise SandboxError(
f"Compound Engineering plugin manifest directory is unavailable: {manifest_root}: {exc}"
) from exc
if stat.S_ISLNK(manifest_root_metadata.st_mode) or not stat.S_ISDIR(manifest_root_metadata.st_mode):
raise SandboxError(f"Compound Engineering plugin manifest directory must be real: {manifest_root}")
required_manifest = _ALLOWED_PLUGIN_MANIFESTS[0]
manifest_path = source / Path(*required_manifest.parts)
if not manifest_path.exists():
raise SandboxError(f"Compound Engineering plugin manifest is missing: {manifest_path}")
for relative in _ALLOWED_PLUGIN_MANIFESTS:
candidate = source / Path(*relative.parts)
if candidate.exists():
yield relative, candidate
skills = source / "skills"
if not skills.exists():
raise SandboxError(f"Compound Engineering plugin skills directory is missing: {skills}")
def walk(directory: Path, relative_dir: PurePosixPath) -> Iterator[tuple[PurePosixPath, Path]]:
try:
with os.scandir(directory) as scanned:
entries = sorted(scanned, key=lambda entry: entry.name)
except OSError as exc:View on GitHub (pinned to d540b00184)
Solutions
- Replace the symlink with a real directory: `rm <plugin>/.claude-plugin && mkdir <plugin>/.claude-plugin && cp -r <real>/.claude-plugin/. <plugin>/.claude-plugin/`.
- Re-extract the plugin tarball with `tar --no-same-owner -xhf` (the `-h` dereferences symlinks at extraction time).
Defensive patterns
Strategy: validation
Validate before calling
import stat
from pathlib import Path
def manifest_dir_is_real(source: Path) -> bool:
p = source / ".claude-plugin"
try:
mode = p.lstat().st_mode
except OSError:
return False
return not stat.S_ISLNK(mode) and stat.S_ISDIR(mode) Try / catch
try:
list(_plugin_files(source))
except SandboxError as exc:
if "manifest directory must be real" in str(exc):
# replace the symlink with a real directory
...
raise Prevention
- Never symlink the .claude-plugin directory.
- Extract plugin tarballs with dereference (`tar -xhf`) to avoid persisting symlinks.
When it happens
Trigger: source/.claude-plugin is a symlink to another location, a regular file, or any non-directory file type.
Common situations: Plugin shipped with a convenience symlink; operator symlinked .claude-plugin from a shared location for testing; tarball extraction preserved a symlink instead of materializing it.
Related errors
- Compound Engineering plugin manifest directory is unavailabl
- Compound Engineering plugin entries must not be symlinks: {e
- Compound Engineering plugin component must be a real directo
- Compound Engineering plugin manifest is invalid: {exc}
- CE comparator requires the compound-engineering plugin manif
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/a5d6c0f43387a351.
Report an issue: GitHub.