ruvnet/ruflo · error · HttpFetchValidationError
INVALID_URL
INVALID_URL
Error message
invalid URL: ${rawUrl} What it means
HttpFetchValidationError with code INVALID_URL, thrown by validateUrl() (http-fetch-tools.ts:51) when `new URL(rawUrl)` throws — i.e. the string is not an absolute, parseable URL. The http_fetch tool only accepts absolute http(s) URLs, so URLs that a browser might auto-correct (missing scheme) are rejected here.
Solutions
- Always include the scheme: 'https://example.com/path'
- Trim whitespace and encode the URL before sending: encodeURI(raw.trim())
- Build from a base: new URL(path, baseUrl).toString() guarantees a valid absolute URL
Example fix
// before
await callTool('http_fetch', { url: 'example.com/api/data' });
// throws HttpFetchValidationError: invalid URL (INVALID_URL)
// after
await callTool('http_fetch', { url: new URL('/api/data', 'https://example.com').toString() }); Defensive patterns
Strategy: validation
Validate before calling
function toAbsoluteHttpUrl(raw: string): string {
const trimmed = raw.trim();
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
return new URL(withScheme).toString(); // throws here = genuinely invalid
}
const url = toAbsoluteHttpUrl(userInput); Type guard
function isParseableUrl(v: string): boolean {
try { new URL(v); return true; } catch { return false; }
} Try / catch
try {
await callTool('http_fetch', { url });
} catch (e) {
if (e instanceof HttpFetchValidationError && e.code === 'INVALID_URL') {
return { error: 'bad_request', detail: 'send a fully qualified http(s) URL' };
}
throw e;
} Prevention
- Always construct URLs with new URL(path, base) rather than string concatenation
- Trim and encode user-supplied URLs before passing them to the tool
When it happens
Trigger: Calling http_fetch with 'example.com/path' (no scheme), 'http//example.com' (missing colon), leading/trailing whitespace, control characters, or unencoded brackets. Only fully qualified absolute URLs parse; relative paths like '/api/v1' also fail.
Common situations: Copy-pasting a bare domain from a browser bar; user input where 'https://' was stripped; concatenating a base URL and path with a missing slash; URLs with spaces or unicode not yet encoded.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- FORBIDDEN_PROTOCOL
- HTTP transport requires host and port configuration
- at least one candidate is required
- candidate must ingest at least one vector
- Dangerous key segment rejected
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/7aba912ba532475b.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts:51
export class HttpFetchValidationError extends Error {
constructor(message: string, public readonly code: string) {
super(message);
this.name = 'HttpFetchValidationError';
}
}
/**
* Decide whether the URL is permitted under the default secure-by-default
* allowlist. Block file://, ftp://, RFC-1918 private addresses, loopback,
* link-local — unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 is set.
*/
export function validateUrl(rawUrl: string): URL {
let parsed: URL;
try {
parsed = new URL(rawUrl);
} catch {
throw new HttpFetchValidationError(`invalid URL: ${rawUrl}`, 'INVALID_URL');
}
const proto = parsed.protocol.toLowerCase();
if (proto !== 'http:' && proto !== 'https:') {
throw new HttpFetchValidationError(
`protocol ${parsed.protocol} not allowed (only http: and https:)`,
'FORBIDDEN_PROTOCOL',
);
}
const host = parsed.hostname.toLowerCase();
const allowPrivate = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE === '1';
if (!allowPrivate && isPrivateOrLoopback(host)) {
throw new HttpFetchValidationError(
`host ${host} is loopback/private/link-local; set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 to override`,
'PRIVATE_ADDRESS',
);
}
return parsed;
}View on GitHub (pinned to fa13ee4ad6)