affaan-m/ECC · error · Error
unsupported plan-canvas request path
Error message
unsupported plan-canvas request path: ${url.pathname} What it means
The request path must resolve to either one of SAFE_REQUEST_PATHS (the fixed API endpoints like /api/sessions, /api/health) or match SESSION_REPLY_PATH (per-session reply routes). Any other pathname is rejected to keep the local server surface minimal.
Solutions
- Use an endpoint listed in SAFE_REQUEST_PATHS at the top of scripts/plan-canvas.js (e.g. GET /api/sessions, POST /api/sessions).
- For session replies, follow the SESSION_REPLY_PATH pattern exactly (correct session id shape).
- Read the route table in the script to confirm the exact path spelling.
- If a new endpoint is needed, add it both server-side and to SAFE_REQUEST_PATHS.
Example fix
// before await request(port, 'GET', '/api/session-list'); // after await request(port, 'GET', '/api/sessions');
Defensive patterns
Strategy: validation
Validate before calling
const SAFE = new Set(['/api/health','/api/sessions','/api/open']);
if (!SAFE.has(pathname) && !/^\/api\/sessions\/[^/]+\/reply$/.test(pathname)) {
throw new Error(`unsupported plan-canvas request path: ${pathname}`);
} Type guard
function isSafeRequestPath(p) {
try { const u = new URL(p, 'http://127.0.0.1');
return SAFE_REQUEST_PATHS.has(u.pathname) || SESSION_REPLY_PATH.test(u.pathname);
} catch { return false; }
} Try / catch
try {
const res = await request(port, method, requestPath);
} catch (err) {
if (err.message.startsWith('unsupported plan-canvas request path:')) {
console.error(`${err.message} — see SAFE_REQUEST_PATHS in scripts/plan-canvas.js`);
process.exit(2);
}
throw err;
} Prevention
- Import/reuse the SAFE_REQUEST_PATHS constant rather than hardcoding routes
- Check the server's route table when upgrading — endpoints can change between versions
- Write tests that exercise each endpoint constant so renames break tests, not runtime
- Match SESSION_REPLY_PATH exactly when constructing per-session URLs
When it happens
Trigger: Calling the request helper with a made-up endpoint such as '/api/v2/sessions', a typo like '/api/session', or a deleted route — the `!SAFE_REQUEST_PATHS.has(url.pathname) && !SESSION_REPLY_PATH.test(url.pathname)` condition becomes true.
Common situations: Guessing API routes from REST conventions; version drift after the server endpoints changed; typos in singular/plural route names; tooling constructing reply URLs that don't match the expected session-id pattern.
Understand the failure class
Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.
Related errors
- download declares an invalid or excessive size
- page_size must be greater than 0
- (payload.error || status + ' ' + statusText)
- plan-canvas request path must be root-relative
- returned
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/7071a4a13e1ec21e.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/plan-canvas.js:110
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,
headers
};
}
function request(port, method, requestPath, body = null) {
return new Promise((resolve, reject) => {
const payload = body === null ? null : JSON.stringify(body);View on GitHub (pinned to 8321021c54)