affaan-m/ECC · error · Error
open failed (HTTP )
Error message
open failed (HTTP ${res.statusCode}) What it means
cmdOpen in the plan-canvas CLI opens (or reopens) a canvas session by POSTing the file path to the local canvas server. The server is expected to answer 200 on success or 409 when the session is already open (in which case the body is returned as-is). Any other HTTP status means the server rejected the request, and since the error body carried no message, the CLI surfaces a generic `open failed (HTTP <status>)` error.
Solutions
- Check that the file path passed to `open` exists and is correct (run `ls` on the resolved path) — most non-200/409 statuses come from the server rejecting the path.
- Look at the server logs or state dir for the real error body; rerun with the server in the foreground to see why /api/sessions rejected the request.
- If the server version is stale (e.g. after upgrading ECC), stop it with `ecc-plan-canvas stop` and reopen so a current server process handles the request.
- Verify nothing else is bound to the port (lsof/netstat) and that no proxy env vars (HTTP_PROXY) intercept 127.0.0.1; unset them or add a NO_PROXY for localhost.
- Retry after fixing; if the response body included an error field, the CLI would have shown that message instead — treat the bare status code as a signal the body was unexpected and inspect raw traffic.
Example fix
// before (path may not exist, server 404s)
await ensureServer({ stateDir, port });
const res = await request(port, 'POST', '/api/sessions', { file: path.resolve(file) });
// after (validate before calling)
const resolved = path.resolve(file);
if (!fs.existsSync(resolved)) {
throw new Error(`plan file not found: ${resolved}`);
}
const res = await request(port, 'POST', '/api/sessions', { file: resolved }); Defensive patterns
Strategy: try-catch
Validate before calling
const resolved = path.resolve(file);
if (!fs.existsSync(resolved)) throw new Error(`plan file not found: ${resolved}`);
if (!(await healthCheck(port))) throw new Error('canvas server not running'); Try / catch
try {
const res = await request(port, 'POST', '/api/sessions', { file: resolved });
if (res.statusCode !== 200 && res.statusCode !== 409) {
throw new Error(res.body?.error || `open failed (HTTP ${res.statusCode})`);
}
} catch (err) {
console.error('open failed:', err.message);
// inspect server logs; consider `ecc-plan-canvas stop` and reopen
} Prevention
- Always verify the plan file exists before calling open.
- Restart the canvas server after ECC upgrades so client and server routes match.
- Set NO_PROXY=localhost so proxies don't intercept the local API call.
- Prefer error bodies (res.body.error) in your own logging to preserve the server's reason.
When it happens
Trigger: Running `ecc-plan-canvas open <file>` when the server responds with a status other than 200 or 409 — e.g. 400 for an invalid/missing file path, 404 when the resolved file does not exist, 500 on a server-side failure writing session state, or 502/503 if a proxy intercepts the localhost request. It also fires when `res.body` is not the expected JSON envelope so `res.body.error` is undefined.
Common situations: Passing a relative or mistyped file path so path.resolve points at a nonexistent artifact; the canvas server crashed or is a stale process from a previous version that no longer understands the POST /api/sessions payload; port contention causing the request to hit a different service; a corporate proxy intercepting localhost HTTP traffic.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- typing failed (HTTP )
- await requires a file path
- end requires a file path
- invalid plan-canvas session key
- typing requires a file path
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/413574c839d9ead9.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/plan-canvas.js:233
async function cmdStatus({ stateDir, port }) {
const health = await healthCheck(port);
if (!health) {
return { server: 'not running', hint: 'open an artifact to start one', stateDir };
}
const sessions = await request(port, 'GET', '/api/sessions');
return { server: `http://${DEFAULT_HOST}:${port}`, version: health.version, sessions: sessions.body.sessions };
}
async function cmdOpen(file, args, { stateDir, port }) {
if (!file) throw new Error('open requires a file path');
if (!fs.existsSync(path.resolve(file))) throw new Error(`artifact not found: ${file}`);
await ensureServer({ stateDir, port });
const res = await request(port, 'POST', '/api/sessions', {
file: path.resolve(file),
reopen: args.includes('--reopen')
});
if (res.statusCode === 409) return res.body;
if (res.statusCode !== 200) throw new Error(res.body.error || `open failed (HTTP ${res.statusCode})`);
const url = `http://${DEFAULT_HOST}:${port}${res.body.url}`;
const launched = args.includes('--no-open') ? false : openBrowser(url);
return {
status: 'open',
url,
browser: launched ? 'opened' : 'not opened',
next_step:
'Run `ecc-plan-canvas await <file>` and leave it running; it returns when the human sends feedback, a verdict, or ends the session.'
};
}
function awaitRequest(port, key, timeoutMs) {
if (!/^[a-f0-9]{12}$/.test(key)) throw new Error('invalid plan-canvas session key');
const params = new URLSearchParams({ key });
if (timeoutMs !== null) params.set('timeoutMs', String(timeoutMs));
return new Promise((resolve, reject) => {
const req = http.request(
requestOptions(port, 'GET', `/api/await?${params}`, {}),View on GitHub (pinned to 8321021c54)