{"record":{"id":"f669e8e7668ee8bf","repo":"abhigyanpatwari/GitNexus","slug":"compound-engineering-plugin-file-must-be-regular-a","errorCode":null,"errorMessage":"Compound Engineering plugin file must be regular and non-symlink: {path}","messagePattern":"Compound Engineering plugin file must be regular and non-symlink: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/runtime_mounts.py","lineNumber":356,"sourceCode":"            continue\n        try:\n            metadata = directory.lstat()\n        except OSError as exc:\n            raise SandboxError(f\"Compound Engineering plugin component is unreadable: {directory}: {exc}\") from exc\n        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n            raise SandboxError(f\"Compound Engineering plugin component must be a real directory: {directory}\")\n        yield from walk(directory, PurePosixPath(name))\n\n\ndef _bounded_plugin_bytes(path: Path) -> tuple[bytes, bool]:\n    \"\"\"Read one stable regular file without following a last-component symlink.\"\"\"\n\n    try:\n        before = path.lstat()\n    except OSError as exc:\n        raise SandboxError(f\"Compound Engineering plugin file is unreadable: {path}: {exc}\") from exc\n    if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):\n        raise SandboxError(f\"Compound Engineering plugin file must be regular and non-symlink: {path}\")\n    if before.st_size > MAX_CE_PLUGIN_FILE_BYTES:\n        raise SandboxError(f\"Compound Engineering plugin file exceeds the per-file limit: {path}\")\n    descriptor = os.open(path, os.O_RDONLY | getattr(os, \"O_NOFOLLOW\", 0))\n    try:\n        opened = os.fstat(descriptor)\n        if (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino) or not stat.S_ISREG(opened.st_mode):\n            raise SandboxError(f\"Compound Engineering plugin file changed during validation: {path}\")\n        chunks: list[bytes] = []\n        remaining = MAX_CE_PLUGIN_FILE_BYTES + 1\n        while remaining > 0:\n            chunk = os.read(descriptor, min(64 * 1024, remaining))\n            if not chunk:\n                break\n            chunks.append(chunk)\n            remaining -= len(chunk)\n        payload = b\"\".join(chunks)\n        after = os.fstat(descriptor)\n    finally:","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/runtime_mounts.py#L338-L374","documentation":"Raised by _bounded_plugin_bytes when a file inside the Compound Engineering comparator plugin source tree is a symlink or anything other than a regular file (fifo, socket, device). The harness refuses to follow last-component symlinks so a malicious or accidental link cannot point content outside the validated plugin root. The check uses lstat, so the link itself is the violation, not its target.","triggerScenarios":"A file under one of _ALLOWED_PLUGIN_DIRS (skills, scripts, assets) or .claude-plugin is a symbolic link, or is a non-regular inode. Common producers: 'npm link'/'pnpm link' dev installs, 'stow', a wrapper that symlinks shared assets, or a checkout where node_modules-style dedup created symlinks.","commonSituations":"Authoring the CE plugin in a monorepo and symlinking shared code into skills/; using 'ln -s' to assemble a release tree; a CI step that creates relative symlinks for path compatibility; shipping the plugin from a read-only mirror via symlinks.","solutions":["Replace every symlink in the plugin tree with a real file copy: 'cp -RL <plugin_src> <fixed_plugin_src>' then point --ce-plugin-dir at the copy.","Find offenders before running: 'find <plugin_dir> -type l' and resolve each by copying the target file in place.","Repackage the plugin so shared files are physically duplicated per skill rather than symlinked.","If a directory component is involved, confirm it is a real directory (error 540's sibling at line 344 fires for symlinked dirs)."],"exampleFix":"# before\nln -s ../../shared/prompts.md skills/ce-plan/prompts.md\n# after\ncp ../../shared/prompts.md skills/ce-plan/prompts.md","handlingStrategy":"validation","validationCode":"import os, stat\nfrom pathlib import Path\n\ndef assert_plugin_files_regular(root: Path) -> list[Path]:\n    bad: list[Path] = []\n    allowed = (\"skills\", \"scripts\", \"assets\", \".claude-plugin\")\n    for base in allowed:\n        d = root / base\n        if not d.exists():\n            continue\n        for dirpath, _dirs, files in os.walk(d, followlinks=False):\n            for name in files:\n                p = Path(dirpath, name)\n                mode = p.lstat().st_mode\n                if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):\n                    bad.append(p)\n    if bad:\n        raise SystemExit(f\"non-regular plugin files: {bad}\")\n    return bad\n\n# run before --ce-plugin-dir\nassert_plugin_files_regular(Path(\"ce-plugin\"))","typeGuard":"import stat\nfrom pathlib import Path\n\ndef is_regular_nonsymlink(path: Path) -> bool:\n    try:\n        mode = path.lstat().st_mode\n    except OSError:\n        return False\n    return stat.S_ISREG(mode) and not stat.S_ISLNK(mode)","tryCatchPattern":"# Not recoverable mid-run. Pre-validate the tree (above) and fix the source.\n# Catching SandboxError only lets you report and exit cleanly:\ntry:\n    staged_ce_plugin_snapshot(config, destination_parent=runtime_root)\nexcept SandboxError as exc:\n    log.error(\"plugin staging failed: %s\", exc)\n    raise","preventionTips":["Build plugin release artifacts with 'cp -aL' (dereference) so symlinks never ship.","Never develop the plugin with 'npm link' against the tree you pass to --ce-plugin-dir; build, then copy.","Add a CI check: 'find <plugin_dir> -type l' must be empty before release."],"tags":["sandbox","ce-plugin","symlink","validation","workflow-bench","filesystem"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}