affaan-m/ECC · error · Error

plan-canvas request path must be root-relative

Error message

plan-canvas request path must be root-relative

What it means

validateRequestPath requires the HTTP request path passed to requestOptions to be a string starting with '/'. Relative paths without a leading slash are rejected because URL resolution against the loopback base would silently change the meaning of the request.

Solutions

  1. Prefix the path with '/', e.g. '/api/sessions'.
  2. Use one of the known endpoints (SAFE_REQUEST_PATHS) or a session reply path matching SESSION_REPLY_PATH.
  3. If building dynamically, normalize: `const p = raw.startsWith('/') ? raw : '/' + raw`.
  4. Check that the variable holding the path is not undefined/null before calling.

Example fix

// before
await request(port, 'GET', 'api/sessions');
// after
await request(port, 'GET', '/api/sessions');
Defensive patterns

Strategy: validation

Validate before calling

function normalizePath(p) {
  if (typeof p !== 'string' || p.length === 0) throw new Error('request path must be a non-empty string');
  return p.startsWith('/') ? p : '/' + p;
}

Type guard

function isRootRelativePath(v) { return typeof v === 'string' && v.startsWith('/') && !v.startsWith('//'); }

Try / catch

try {
  const res = await request(port, method, requestPath);
} catch (err) {
  if (err.message === 'plan-canvas request path must be root-relative') {
    console.error('Request paths must begin with /');
    process.exit(2);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the internal request helper with 'api/sessions' (no leading slash), an empty string, or a non-string value (null/undefined from a missing argument) hits the `!requestPath.startsWith('/')` guard.

Common situations: Programmatic callers building paths from variables and dropping the slash; concatenating a base URL segment; refactors renaming endpoints while forgetting the leading '/'.

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 affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/10c0c79e7f857484. Report an issue: GitHub.

Appendix: source

Thrown at scripts/plan-canvas.js:103

function readServerInfo(stateDir) {
  try {
    return JSON.parse(fs.readFileSync(serverInfoPath(stateDir), 'utf8'));
  } catch {
    return null;
  }
}

function validatePort(port) {
  const value = Number(port);
  if (!Number.isInteger(value) || value < 0 || value > 65535) {
    throw new Error(`invalid plan-canvas server port: ${port}`);
  }
  return value;
}

function validateRequestPath(requestPath) {
  if (typeof requestPath !== 'string' || !requestPath.startsWith('/')) {
    throw new Error('plan-canvas request path must be root-relative');
  }
  const url = new URL(requestPath, `http://${DEFAULT_HOST}`);
  if (url.hostname !== DEFAULT_HOST) {
    throw new Error('plan-canvas request path must stay on the loopback server');
  }
  if (!SAFE_REQUEST_PATHS.has(url.pathname) && !SESSION_REPLY_PATH.test(url.pathname)) {
    throw new Error(`unsupported plan-canvas request path: ${url.pathname}`);
  }
  return `${url.pathname}${url.search}`;
}

function requestOptions(port, method, requestPath, headers) {
  return {
    host: DEFAULT_HOST,
    port: validatePort(port),
    method,
    path: validateRequestPath(requestPath),
    agent: false,

View on GitHub (pinned to 8321021c54)