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

Thrown by validateRequestPath in scripts/plan-canvas.js when the outbound request path is not a string or does not begin with '/'. This is the first of three guards validating any HTTP path the CLI sends to the loopback canvas server; it enforces that paths are root-relative before URL parsing.

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 01e15490f0)

Solutions

  1. Ensure request paths passed to request() always start with '/', e.g. '/api/sessions'.
  2. When appending query strings, build them onto a rooted pathname: `/api/await?${params}`.
  3. If you see this as an end user, it is a bug in plan-canvas.js itself — report it rather than working around it.

Example fix

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

Strategy: validation

Validate before calling

function ensureRootedPath(p) {
  if (typeof p !== 'string' || !p.startsWith('/')) {
    throw new Error(`Request path must be root-relative: ${String(p)}`);
  }
  return p;
}

Type guard

function isRootedPath(value) {
  return typeof value === 'string' && value.startsWith('/');
}

Prevention

When it happens

Trigger: Calling request() (internal) with a path like 'api/sessions' (no leading slash), an empty string, or a non-string value. This is an internal-API guard — end users normally never set request paths directly; it surfaces via bugs in CLI command code that construct a path without a leading slash.

Common situations: A code change that builds a request path dynamically and omits the leading slash; refactoring that passes a query string or full URL where a pathname is expected.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/10c0c79e7f857484. Report an issue: GitHub.