{"record":{"id":"423f1450fae4a558","repo":"abhigyanpatwari/GitNexus","slug":"compound-engineering-plugin-file-is-unreadable-p","errorCode":null,"errorMessage":"Compound Engineering plugin file is unreadable: {path}: {exc}","messagePattern":"Compound Engineering plugin file is unreadable: (.+?): (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/runtime_mounts.py","lineNumber":354,"sourceCode":"        directory = source / name\n        if not directory.exists():\n            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)","sourceCodeStart":336,"sourceCodeEnd":372,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/runtime_mounts.py#L336-L372","documentation":"_bounded_plugin_bytes() lstat's each plugin file before opening it with O_NOFOLLOW to copy the bytes into the snapshot. OSError on that initial lstat (file removed, permission denied, I/O error) raises this error so the harness never silently skips a file the walk already emitted.","triggerScenarios":"A file emitted by walk() is removed or has its permissions changed before _bounded_plugin_bytes() runs; NFS stale handle; concurrent writer.","commonSituations":"Concurrent build/editing of the plugin tree during snapshot creation; ACL applied between enumeration and copy; flaky network filesystem.","solutions":["Freeze the plugin tree before running the benchmark: stop concurrent writers, copy to a local readonly directory.","Grant read on every file: `chmod -R +r <plugin_dir>`.","Re-run the snapshot build against the stable copy."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef all_files_readable(source: Path) -> bool:\n    for name in (\"skills\", \"scripts\", \"assets\"):\n        d = source / name\n        if not d.exists():\n            continue\n        for p in d.rglob(\"*\"):\n            if p.is_file():\n                try:\n                    p.lstat()\n                except OSError:\n                    return False\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    snapshot = _build_ce_plugin_snapshot(config, parent)\nexcept SandboxError as exc:\n    if \"file is unreadable\" in str(exc):\n        # freeze tree + grant read, then retry\n        ...\n    raise","preventionTips":["Freeze the plugin source during the snapshot build (no concurrent writes).","Copy the plugin to a local readonly directory and benchmark against the copy.","Ensure `chmod -R +r` covers every regular file in the source tree."],"tags":["ce-plugin","permissions","filesystem","race-condition"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}