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
- 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.
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
- 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 '/'
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
- cannot upload, file does not exist
- --config-dir must be an existing absolute directory.
- --cwd must be an absolute path.
- --cwd must not contain a NUL byte.
- download declares an invalid or excessive size
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)