{"record":{"id":"00fd330d9c44d2e4","repo":"can1357/oh-my-pi","slug":"protocol-paths-are-not-supported-by-this-helper","errorCode":null,"errorMessage":"Protocol paths are not supported by this helper: {path}","messagePattern":"Protocol paths are not supported by this helper: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/py/prelude.py","lineNumber":102,"sourceCode":"\n        A `scheme://…` whose scheme has an injected on-disk root (e.g.\n        `local://`, via PI_EVAL_LOCAL_ROOTS) is rewritten under that root so it\n        lands where `read local://…` resolves — not a literal `local:/`\n        directory under the cwd (which `Path(\"local://x\")` collapses to). Plain\n        paths pass through unchanged; any other `scheme://` is rejected.\"\"\"\n        if not isinstance(path, str):\n            return Path(path)\n        match = _OMP_INTERNAL_URL_RE.match(path)\n        if not match:\n            return Path(path)\n        scheme = match.group(1).lower()\n        try:\n            roots = json.loads(os.environ.get(\"PI_EVAL_LOCAL_ROOTS\") or \"{}\")\n        except (ValueError, TypeError):\n            roots = {}\n        root = roots.get(scheme) if isinstance(roots, dict) else None\n        if not root:\n            raise ValueError(f\"Protocol paths are not supported by this helper: {path}\")\n        relative = unquote(match.group(2).replace(\"\\\\\", \"/\"))\n        # Mirror the host `path.resolve`/`resolveLocalUrlToPath`: normalize and\n        # make absolute WITHOUT realpath'ing symlinks (Path.resolve would turn\n        # /tmp into /private/tmp and diverge from the read-side resolution).\n        root_path = os.path.abspath(root)\n        if relative == \"\":\n            return Path(root_path)\n        rel_path = Path(relative)\n        if rel_path.is_absolute() or \"..\" in rel_path.parts:\n            raise ValueError(f\"Unsafe {scheme}:// path (absolute or traversal): {path}\")\n        resolved = os.path.abspath(os.path.join(root_path, relative))\n        if resolved != root_path and not resolved.startswith(root_path + os.sep):\n            raise ValueError(f\"{scheme}:// path escapes its root: {path}\")\n        return Path(resolved)\n\n    def read(path: str | Path, offset: int = 1, limit: int | None = None) -> str:\n        \"\"\"Read file or read-tool URI contents. offset/limit are 1-indexed lines.\"\"\"\n        if _should_delegate_read(path):","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/py/prelude.py#L84-L120","documentation":"The eval Python prelude's _resolve_omp_path maps scheme-based paths (e.g. file:// or omp:// URIs) onto a host root directory supplied via the PI_EVAL_LOCAL_ROOTS env var (a JSON map of scheme -> root). If the URL's scheme has no entry in that map (or the env var is absent/malformed), it raises ValueError 'Protocol paths are not supported by this helper'. read() and write() call this resolver for every path they receive.","triggerScenarios":"Calling prelude read()/write() with a URL-style path whose scheme (e.g. 's3://', 'gs://', or an 'omp://' tool URI) is missing from PI_EVAL_LOCAL_ROOTS, PI_EVAL_LOCAL_ROOTS unset or invalid JSON, or the scheme key having a different case/spelling than the map's keys.","commonSituations":"Model-generated code passing a tool-URI it saw in tool output instead of a local path; running the prelude outside the eval harness where PI_EVAL_LOCAL_ROOTS was never set; harness changes renaming a scheme.","solutions":["Set PI_EVAL_LOCAL_ROOTS to valid JSON mapping the scheme to a local root dir, e.g. {\"omp\": \"/tmp/session-artifacts\"}.","Pass a plain local filesystem path to read()/write() instead of a scheme URI.","Verify the scheme key casing matches what the harness injects into PI_EVAL_LOCAL_ROOTS."],"exampleFix":"# before\ncontent = read(\"s3://bucket/data.csv\")  # no 's3' root configured\n# after\nimport os\nos.environ.setdefault(\"PI_EVAL_LOCAL_ROOTS\", '{\"s3\": \"/tmp/s3-mirror\"}')\ncontent = read(\"s3://bucket/data.csv\")  # resolves to /tmp/s3-mirror/bucket/data.csv","handlingStrategy":"validation","validationCode":"import json, os\nroots = json.loads(os.environ.get('PI_EVAL_LOCAL_ROOTS') or '{}')\nscheme = path.split('://', 1)[0] if '://' in path else None\nif scheme and scheme not in roots:\n    raise ValueError(f'Scheme {scheme}:// not configured in PI_EVAL_LOCAL_ROOTS')","typeGuard":"def is_protocol_path(p): return '://' in str(p)","tryCatchPattern":"try:\n    content = read(path)\nexcept ValueError as e:\n    if 'Protocol paths are not supported' in str(e):\n        content = read(local_fallback_path)\n    else:\n        raise","preventionTips":["Ensure the harness exports PI_EVAL_LOCAL_ROOTS before executing prelude code.","Prefer plain local paths inside the eval sandbox over scheme URIs.","Keep scheme keys in sync with the harness's URI scheme naming.","Validate PI_EVAL_LOCAL_ROOTS parses as JSON at session start."],"tags":["python","path-resolution","environment","prelude"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}