Bug
When OPENCLAW_CLAWHUB_URL (or CLAWHUB_URL) is set to a URL that includes a non-root path prefix (e.g. http://host:port/prefix), all ClawHub API calls return 404.
Root Cause
src/infra/clawhub.ts → buildUrl() (line 553):
const url = new URL(params.path, `${normalizeBaseUrl(params.baseUrl)}/`);
new URL() treats a leading-/ path as absolute and replaces the base URL's entire pathname:
new URL("/api/v1/search", "http://host:port/prefix/")
→ "http://host:port/api/v1/search" // /prefix dropped
Expected: http://host:port/prefix/api/v1/search
This affects every endpoint (/api/v1/search, /api/v1/skills/*, /api/v1/packages/*, /api/v1/download, etc.).
Reproduction
# With a reverse-proxied ClawHub instance behind a path prefix:
export OPENCLAW_CLAWHUB_URL=http://clawhub.internal:8080/clawhub
# Works (clawhub CLI):
clawhub search "foo" # → results
# Broken (openclaw):
openclaw skills search "foo" # → ClawHubRequestError: 404 Not Found
Direct curl to the full path confirms the endpoint is alive:
curl "http://clawhub.internal:8080/clawhub/api/v1/search?q=foo" # 200 OK
curl "http://clawhub.internal:8080/api/v1/search?q=foo" # 404 (what openclaw actually hits)
Suggested Fix
Replace new URL(path, base) with string concatenation to preserve the base path:
function buildUrl(params: Pick<ClawHubRequestParams, "baseUrl" | "path" | "search">): URL {
const base = normalizeBaseUrl(params.baseUrl);
const url = new URL(`${base}${params.path}`);
for (const [key, value] of Object.entries(params.search ?? {})) {
if (!value) continue;
url.searchParams.set(key, value);
}
return url;
}
Or alternatively, ensure normalizeBaseUrl strips trailing slashes and params.path is joined without the leading /.
Environment
- openclaw version: current
main (commit 041266a)
- Deployment: Docker container, ClawHub behind nginx reverse proxy with URL prefix
Bug
When
OPENCLAW_CLAWHUB_URL(orCLAWHUB_URL) is set to a URL that includes a non-root path prefix (e.g.http://host:port/prefix), all ClawHub API calls return 404.Root Cause
src/infra/clawhub.ts→buildUrl()(line 553):new URL()treats a leading-/path as absolute and replaces the base URL's entire pathname:Expected:
http://host:port/prefix/api/v1/searchThis affects every endpoint (
/api/v1/search,/api/v1/skills/*,/api/v1/packages/*,/api/v1/download, etc.).Reproduction
Direct
curlto the full path confirms the endpoint is alive:Suggested Fix
Replace
new URL(path, base)with string concatenation to preserve the base path:Or alternatively, ensure
normalizeBaseUrlstrips trailing slashes andparams.pathis joined without the leading/.Environment
main(commit041266a)