pbakaus/impeccable · error · Error

invalid session id

Error message

invalid session id: ${id}

What it means

Thrown by safeSessionId when a session id fails the pattern ^[A-Za-z0-9_-]{1,128}$ or is not a string. Session ids become path segments under .impeccable/live/ (journals, snapshots, accept receipts, preview manifests, generated component dirs) and arrive from untrusted surfaces — CLI --id arguments and HTTP payloads — so separators, '..', or over-long ids must be rejected before path.join can escape the sessions directory. Genuine ids are 8 hex characters; tests use short slugs.

Solutions

  1. Use the exact id printed when the session was created (8 hex chars).
  2. If generating ids yourself, stick to [A-Za-z0-9_-] and at most 128 chars — e.g. crypto.randomBytes(4).toString('hex').
  3. Send ids as strings in HTTP payloads, never numbers or objects.
  4. Remove any path separators, dots, or whitespace before passing the id.

Example fix

// before
const id = req.body.id;                 // 12345 (number) or "../oops"
const dir = path.join(sessionsDir, id);   // traversal / NaN-ish join

// after
import { safeSessionId } from './lib/impeccable-paths.mjs';
const id = safeSessionId(String(req.body.id));   // throws early on bad input
const dir = path.join(sessionsDir, id);
Defensive patterns

Strategy: type-guard

Validate before calling

const SESSION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
function isValidSessionId(id) {
  return typeof id === 'string' && SESSION_ID_RE.test(id);
}
// gate untrusted input (CLI --id, HTTP payload) before any path work:
if (!isValidSessionId(inputId)) return res.status(400).json({ error: 'invalid session id' });
const dir = path.join(sessionsDir, safeSessionId(inputId));

Type guard

import { safeSessionId } from './lib/impeccable-paths.mjs';
function asSessionId(id) {
  try { return safeSessionId(id); } catch { return null; }
}
const id = asSessionId(req.body?.id);
if (id === null) { /* reject request */ }

Try / catch

try {
  const id = safeSessionId(raw);
} catch (err) {
  if (/^invalid session id/.test(err.message)) {
    // untrusted input problem: reject at the trust boundary, never retry or sanitize by stripping characters
    return badRequest('session id must match [A-Za-z0-9_-]{1,128}');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing `--id ../../etc` or `--id foo/bar` (path separators); an id with spaces or unicode punctuation from a copy-paste; `--id` with an undefined/empty value interpolated into a request payload; an HTTP client posting {"id": 12345} as a number rather than a string.

Common situations: Hand-typed ids drifting from the 8-hex-char convention; agents fabricating ids instead of reading them from session output; API consumers assuming any unique string is acceptable; attempt (accidental or deliberate) to traverse out of the live-sessions directory.

Related errors


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/dc8f6c1efceb94b8. Report an issue: GitHub.

Appendix: source

Thrown at skill/scripts/lib/impeccable-paths.mjs:110

  return filePath;
}

export function removeLiveServerInfo(cwd = process.cwd(), options = {}) {
  for (const filePath of [getLiveServerPath(cwd, options), getLegacyLiveServerPath(cwd, options)]) {
    try { fs.unlinkSync(filePath); } catch {}
  }
}

/**
 * Session IDs become path segments (journals, snapshots, accept receipts,
 * preview manifests, generated component dirs). They arrive from CLI `--id`
 * arguments and HTTP payloads, so anything containing a separator or `..` must
 * be rejected before it reaches path.join, which would happily escape
 * `.impeccable/live/`. Real IDs are 8 hex chars; the tests use short slugs.
 */
export function safeSessionId(id) {
  if (typeof id !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(id)) {
    throw new Error('invalid session id: ' + id);
  }
  return id;
}

export function getLiveSessionsDir(cwd = process.cwd(), options = {}) {
  return path.join(getLiveDir(cwd, options), 'sessions');
}

export function getLegacyLiveSessionsDir(cwd = process.cwd(), options = {}) {
  return path.join(resolveProjectRoot(cwd, options), '.impeccable-live', 'sessions');
}

export function getLiveAnnotationsDir(cwd = process.cwd(), options = {}) {
  return path.join(getLiveDir(cwd, options), 'annotations');
}

export function getCritiqueDir(cwd = process.cwd(), options = {}) {
  return path.join(getImpeccableDir(cwd, options), CRITIQUE_DIR);

View on GitHub (pinned to f88b2837a7)