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

  1. Always derive the key with sessionKeyFor(canonicalizeArtifactPath(file)) rather than constructing one by hand.
  2. If you maintain sessionKeyFor, ensure it still emits 12 lowercase hex characters.
  3. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/c808e93b5822962b. Report an issue: GitHub.