affaan-m/ECC · error · Error
invalid plan-canvas session key
Error message
invalid plan-canvas session key
What it means
Thrown by awaitRequest in scripts/plan-canvas.js when the session key does not match the strict pattern `/^[a-f0-9]{12}$/` — exactly 12 lowercase hex characters. Session keys are derived from the artifact path via sessionKeyFor, so a mismatch implies the caller passed a raw/arbitrary string or the derivation produced an unexpected value.
Source
Thrown at scripts/plan-canvas.js:246
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}`, {}),
res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data.trim()));
} catch {
reject(new Error('await response was not JSON (server restarted?) - re-run await; feedback is never lost'));
}
});
}View on GitHub (pinned to 01e15490f0)
Solutions
- Always derive the key with sessionKeyFor(canonicalizeArtifactPath(file)) rather than constructing one by hand.
- If you maintain sessionKeyFor, ensure it still emits 12 lowercase hex characters.
- End users hitting this via the CLI: it signals a bug in path canonicalization — report it with the file path used.
Example fix
// before (internal call, raw id) awaitRequest(port, 'session-1', null); // after const key = sessionKeyFor(canonicalizeArtifactPath(file)); awaitRequest(port, key, null);
Defensive patterns
Strategy: type-guard
Validate before calling
const KEY_RE = /^[a-f0-9]{12}$/;
function assertSessionKey(key) {
if (!KEY_RE.test(key)) throw new Error(`Invalid session key (need 12 lowercase hex chars): ${String(key)}`);
return key;
} Type guard
function isSessionKey(value) {
return typeof value === 'string' && /^[a-f0-9]{12}$/.test(value);
} Prevention
- Always derive keys with sessionKeyFor(canonicalizeArtifactPath(file)); never construct them by hand.
- If you change sessionKeyFor or canonicalizeArtifactPath, regenerate existing fixtures and migration tests.
- End users: treat this as a CLI bug and report the file path that triggered it.
When it happens
Trigger: Calling awaitRequest directly (internal) with a key that is not 12 lowercase hex chars; an uppercase hex key; a key of the wrong length; passing a filename or path where a key is expected. In normal CLI flow cmdAwait computes the key via sessionKeyFor(canonicalizeArtifactPath(file)), so this guard is a defensive check on that derivation.
Common situations: A change to sessionKeyFor or canonicalizeArtifactPath that alters the key format; calling the internal request layer with a user-supplied identifier; a test fixture using a placeholder key like 'test' or 'session-1'.
Related errors
- plan-canvas request path must be root-relative
- invalid plan-canvas server port: ${port}
- plan-canvas request path must stay on the loopback server
- unsupported plan-canvas request path: ${url.pathname}
- Invalid session state transition: {} -> {}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/c808e93b5822962b.
Report an issue: GitHub.