{"record":{"id":"10c0c79e7f857484","repo":"affaan-m/ECC","slug":"plan-canvas-request-path-must-be-root-relative","errorCode":null,"errorMessage":"plan-canvas request path must be root-relative","messagePattern":"plan-canvas request path must be root-relative","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/plan-canvas.js","lineNumber":103,"sourceCode":"function readServerInfo(stateDir) {\n  try {\n    return JSON.parse(fs.readFileSync(serverInfoPath(stateDir), 'utf8'));\n  } catch {\n    return null;\n  }\n}\n\nfunction validatePort(port) {\n  const value = Number(port);\n  if (!Number.isInteger(value) || value < 0 || value > 65535) {\n    throw new Error(`invalid plan-canvas server port: ${port}`);\n  }\n  return value;\n}\n\nfunction validateRequestPath(requestPath) {\n  if (typeof requestPath !== 'string' || !requestPath.startsWith('/')) {\n    throw new Error('plan-canvas request path must be root-relative');\n  }\n  const url = new URL(requestPath, `http://${DEFAULT_HOST}`);\n  if (url.hostname !== DEFAULT_HOST) {\n    throw new Error('plan-canvas request path must stay on the loopback server');\n  }\n  if (!SAFE_REQUEST_PATHS.has(url.pathname) && !SESSION_REPLY_PATH.test(url.pathname)) {\n    throw new Error(`unsupported plan-canvas request path: ${url.pathname}`);\n  }\n  return `${url.pathname}${url.search}`;\n}\n\nfunction requestOptions(port, method, requestPath, headers) {\n  return {\n    host: DEFAULT_HOST,\n    port: validatePort(port),\n    method,\n    path: validateRequestPath(requestPath),\n    agent: false,","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/plan-canvas.js#L85-L121","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure request paths passed to request() always start with '/', e.g. '/api/sessions'.","When appending query strings, build them onto a rooted pathname: `/api/await?${params}`.","If you see this as an end user, it is a bug in plan-canvas.js itself — report it rather than working around it."],"exampleFix":"// before (internal call)\nrequest(port, 'GET', 'api/sessions');\n// after\nrequest(port, 'GET', '/api/sessions');","handlingStrategy":"validation","validationCode":"function ensureRootedPath(p) {\n  if (typeof p !== 'string' || !p.startsWith('/')) {\n    throw new Error(`Request path must be root-relative: ${String(p)}`);\n  }\n  return p;\n}","typeGuard":"function isRootedPath(value) {\n  return typeof value === 'string' && value.startsWith('/');\n}","tryCatchPattern":null,"preventionTips":["Internal callers of request() should always construct paths with a leading slash.","Keep a single helper that builds request paths so the invariant is enforced in one place.","End users: report this as a bug; it should not surface through normal CLI usage."],"tags":["plan-canvas","network","validation","internal-api"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}