{"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":"validation","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/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/plan-canvas.js#L85-L121","documentation":"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.","triggerScenarios":"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.","commonSituations":"Programmatic callers building paths from variables and dropping the slash; concatenating a base URL segment; refactors renaming endpoints while forgetting the leading '/'.","solutions":["Prefix the path with '/', e.g. '/api/sessions'.","Use one of the known endpoints (SAFE_REQUEST_PATHS) or a session reply path matching SESSION_REPLY_PATH.","If building dynamically, normalize: `const p = raw.startsWith('/') ? raw : '/' + raw`.","Check that the variable holding the path is not undefined/null before calling."],"exampleFix":"// before\nawait request(port, 'GET', 'api/sessions');\n// after\nawait request(port, 'GET', '/api/sessions');","handlingStrategy":"validation","validationCode":"function normalizePath(p) {\n  if (typeof p !== 'string' || p.length === 0) throw new Error('request path must be a non-empty string');\n  return p.startsWith('/') ? p : '/' + p;\n}","typeGuard":"function isRootRelativePath(v) { return typeof v === 'string' && v.startsWith('/') && !v.startsWith('//'); }","tryCatchPattern":"try {\n  const res = await request(port, method, requestPath);\n} catch (err) {\n  if (err.message === 'plan-canvas request path must be root-relative') {\n    console.error('Request paths must begin with /');\n    process.exit(2);\n  }\n  throw err;\n}","preventionTips":["Centralize endpoint constants (e.g. PATHS.SESSIONS = '/api/sessions') instead of inline strings","Never build paths by concatenating base URLs into the path argument","Add a unit test asserting every endpoint constant starts with '/'"],"tags":["http","validation","path"],"backgroundTag":"invalid-url-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}