vercel/ai · error

Invalid Pi session file name: ${input.sessionFileName}

Error message

Invalid Pi session file name: ${input.sessionFileName}

What it means

resolveContainedHostPath validates that the Pi session file name resolves to a path strictly contained in the host's local session mirror directory. The name must be a safe basename ending in .jsonl or .json and must not escape the base dir. It throws for empty paths, absolute paths, parent escapes ('..'), or names failing the safe-file-name pattern.

Source

Thrown at packages/harness-pi/src/pi-resume-state.ts:78

  return privateSessionDir;
}

function resolveContainedHostPath(input: {
  readonly baseDir: string;
  readonly sessionFileName: string;
}): string {
  const baseDir = path.resolve(input.baseDir);
  const filePath = path.resolve(
    baseDir,
    safePiSessionFileName(input.sessionFileName),
  );
  const relativePath = path.relative(baseDir, filePath);
  if (
    relativePath === '' ||
    relativePath.startsWith('..') ||
    path.isAbsolute(relativePath)
  ) {
    throw new Error(`Invalid Pi session file name: ${input.sessionFileName}`);
  }
  return filePath;
}

function resolveContainedSandboxPath(input: {
  readonly privateSessionDir: string;
  readonly sessionFileName: string;
}): string {
  const sessionDir = path.posix.resolve(input.privateSessionDir);
  const filePath = path.posix.resolve(
    sessionDir,
    safePiSessionFileName(input.sessionFileName),
  );
  const relativePath = path.posix.relative(sessionDir, filePath);
  if (
    relativePath === '' ||
    relativePath.startsWith('..') ||
    path.posix.isAbsolute(relativePath)

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Use only basenames matching /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/ for sessionFileName
  2. Read sessionFileName from piResumeStateSchema-validated state (it enforces the same pattern)
  3. Strip any directory components from the stored name before calling resume APIs

Example fix

// before
await persistSessionFileToSandbox({
  ...
  sessionFileName: '/sessions/abc.jsonl', // absolute path -> throws
});
// after
await persistSessionFileToSandbox({
  ...
  sessionFileName: 'abc.jsonl', // safe basename
});
Defensive patterns

Strategy: validation

Validate before calling

const SAFE = /^[A-Za-z0-9][A-Za-z0-9._-]*\.jsonl?$/;
export function isValidPiSessionFileName(name: unknown): name is string {
  return typeof name === 'string' && SAFE.test(name);
}

Type guard

export function isPiResumeState(v: unknown): v is { sessionFileName?: string } {
  return typeof v === 'object' && v !== null &&
    (!('sessionFileName' in v) || isValidPiSessionFileName((v as any).sessionFileName));
}

Try / catch

try {
  await persistSessionFileToSandbox({ ...args });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Invalid Pi session file name')) {
    // reject/repair the stored sessionFileName
  }
  throw e;
}

Prevention

When it happens

Trigger: persistSessionFileToSandbox or pullSessionFileFromSandbox receives a sessionFileName that is absolute, empty, contains path traversal ('../'), or is not a `<safe-name>.jsonl`/`.json` basename — typically because it came from a corrupted or tampered resume state's `data` payload.

Common situations: Hand-editing session state to use a full path instead of a basename; restoring state written by another harness version; a hostile/legacy sessionFileName like '../../etc/passwd' or 'session.txt'.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/12c94b337d42d3f1. Report an issue: GitHub.