Skip to content

Install from file:// source misroutes to git clone (is_git_url / is_local_path_source classifier conflict) #4851

Description

@valeriiD

Environment

  • openhands-agent-server / sdk / tools 1.44.1 (pip, stock venv)
  • Windows 11, python -m openhands.agent_server --host 127.0.0.1 --port 8001
  • Re-checked against current main — the classifier conflict below is unchanged

Summary

Correction — my original report overstated the failure set; see my analysis comment for the full three-cause breakdown. Of the three local-source forms tested, only the file:// URI is a server-side bug. The raw-Windows-path failure was a client-side JSON escaping artifact in my own requests (unescaped backslashes — the two-character sequences \b and \r are valid JSON escapes and corrupted the path before it reached the server); with a correctly encoded body the REST endpoints install a raw Windows path fine, verified against the same server. The /r/... MSYS-style path is drive-relative on Windows (not a Windows path) and is expected to fail. What remains is below.

The bug: a file:// source is misclassified as a git URL, so install attempts run git clone against a plain directory and fail.

Steps to Reproduce

SDK-only, no server needed (platform-independent):

python -c "
from openhands.sdk.extensions.fetch import parse_extension_source
from openhands.sdk.git.utils import is_git_url
from openhands.sdk.utils.path import is_local_path_source

src = 'file:///C:/path/to/local/extension'
print(is_git_url(src))             # True  — claims file:// 'for testing'
print(is_local_path_source(src))   # True  — documents file:// as local syntax
print(parse_extension_source(src)) # (SourceType.GIT, ...) — git wins by ordering
"

REST consequence (either router, any OS) — note the body below is correctly escaped JSON, so this failure is genuinely server-side:

curl -X POST .../api/canvas-extensions/install -H 'Content-Type: application/json' \
  -d '{"source": "file:///C:/path/to/local/extension"}'
# -> 400 "Failed to fetch canvas extension source"
#    server log: `git clone --depth 1 file:///C:/path/to/local/extension` → exit 128
#    ("does not appear to be a git repository")

Expected Behavior

  • parse_extension_source("file:///...") classifies the source as SourceType.LOCAL, matching is_local_path_source()'s docstring ("accepts explicit local path syntax such as file:// URLs").
  • Installing from a file:// URI copies the local directory.

Actual Behavior

Running the python snippet above: is_git_url() and is_local_path_source() both return True for the same file:// string, and parse_extension_source() resolves the contradiction in favour of git:

  • openhands/sdk/git/utils.pyis_git_url() returns True for file:// (comment: "File protocol (for testing)").
  • openhands/sdk/extensions/fetch.pyparse_extension_source() checks is_git_url() before is_local_path_source(), so file:// sources are routed to git cloneExtensionFetchError → the routers return 400 "Failed to fetch ... source". The local classifier's file:// branch is unreachable on this path.
  • _resolve_local_source() does bare Path(url) with no URL→path conversion, so the LOCAL branch couldn't handle a file:// URI either (Path('file:///C:/x').resolve() resolves to garbage). The fix needs both halves.

Acceptance Criteria

  • parse_extension_source("file:///path/to/ext") returns SourceType.LOCAL
  • Installing from a file:// URI copies the local directory (no git clone attempt)
  • is_git_url() and is_local_path_source() no longer both claim file://

Suggested fix

Classify file:// as LOCAL in parse_extension_source() (check is_local_path_source() first, or drop the file:// claim from is_git_url()), and teach _resolve_local_source() to convert a file:// URI to a path (strip scheme, urllib.parse.unquote).

Notes

  • file:// is not documented as an install-source form in the REST schema ("git URL, GitHub shorthand, or local path"), so this is an internal-consistency fix rather than a broken documented feature — the documented forms work per-platform (native Windows paths on Windows, POSIX paths on Linux) when request bodies are correctly encoded.
  • Nearest existing work (fix(extensions): compose local source with repo_path #4839, local source + repo_path composition) addresses a different local-source defect.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions