You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 "
fromopenhands.sdk.extensions.fetchimportparse_extension_sourcefromopenhands.sdk.git.utilsimportis_git_urlfromopenhands.sdk.utils.pathimportis_local_path_sourcesrc='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 syntaxprint(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/extensions/fetch.py — parse_extension_source() checks is_git_url()beforeis_local_path_source(), so file:// sources are routed to git clone → ExtensionFetchError → 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.
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.
Environment
python -m openhands.agent_server --host 127.0.0.1 --port 8001main— the classifier conflict below is unchangedSummary
The bug: a
file://source is misclassified as a git URL, so install attempts rungit cloneagainst a plain directory and fail.Steps to Reproduce
SDK-only, no server needed (platform-independent):
REST consequence (either router, any OS) — note the body below is correctly escaped JSON, so this failure is genuinely server-side:
Expected Behavior
parse_extension_source("file:///...")classifies the source asSourceType.LOCAL, matchingis_local_path_source()'s docstring ("accepts explicit local path syntax such asfile://URLs").file://URI copies the local directory.Actual Behavior
Running the python snippet above:
is_git_url()andis_local_path_source()both returnTruefor the samefile://string, andparse_extension_source()resolves the contradiction in favour of git:openhands/sdk/git/utils.py—is_git_url()returnsTrueforfile://(comment: "File protocol (for testing)").openhands/sdk/extensions/fetch.py—parse_extension_source()checksis_git_url()beforeis_local_path_source(), sofile://sources are routed togit clone→ExtensionFetchError→ the routers return 400 "Failed to fetch ... source". The local classifier'sfile://branch is unreachable on this path._resolve_local_source()does barePath(url)with no URL→path conversion, so the LOCAL branch couldn't handle afile://URI either (Path('file:///C:/x').resolve()resolves to garbage). The fix needs both halves.Acceptance Criteria
parse_extension_source("file:///path/to/ext")returnsSourceType.LOCALfile://URI copies the local directory (nogit cloneattempt)is_git_url()andis_local_path_source()no longer both claimfile://Suggested fix
Classify
file://as LOCAL inparse_extension_source()(checkis_local_path_source()first, or drop thefile://claim fromis_git_url()), and teach_resolve_local_source()to convert afile://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.repo_pathcomposition) addresses a different local-source defect.