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

  1. Always include the scheme: 'https://example.com/path'
  2. Trim whitespace and encode the URL before sending: encodeURI(raw.trim())
  3. 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

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


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)