pbakaus/impeccable · error

writeSnapshot requires a slug

Error message

writeSnapshot requires a slug

What it means

writeSnapshot() persists a critique snapshot to disk as `${timestamp}__${slug}.md` and writes `slug` into the YAML-style frontmatter. The slug is load-bearing: it names the file and is read back by readTrend() to group snapshots over time. The guard throws synchronously when slug is falsy because an empty slug would produce a malformed filename (`<timestamp>__.md`) and a frontmatter `slug:` key with no value, corrupting trend aggregation.

Source

Thrown at plugin/skills/impeccable/scripts/critique-storage.mjs:61

 */
/**
 * Filename-safe UTC ISO timestamp: hyphens for separators, trailing Z.
 * Plain colons aren't allowed on Windows filesystems.
 */
export function nowFilenameStamp(date = new Date()) {
  const iso = date.toISOString();           // 2026-05-12T18:30:00.123Z
  return iso.replace(/[:.]/g, '-').replace(/-\d+Z$/, 'Z');
}

/**
 * Write a snapshot for `slug`. `meta` carries the small structured frontmatter
 * keys read back by readTrend(). `body` is the human-readable critique
 * report (everything below the frontmatter).
 *
 * Returns the absolute path written.
 */
export function writeSnapshot({ slug, meta, body, cwd = process.cwd(), now = new Date() }) {
  if (!slug) throw new Error('writeSnapshot requires a slug');
  const dir = getCritiqueDir(cwd);
  fs.mkdirSync(dir, { recursive: true });
  const timestamp = nowFilenameStamp(now);
  const filePath = path.join(dir, `${timestamp}__${slug}.md`);
  // Spread `meta` first so internally computed `timestamp` and `slug`
  // always win. Otherwise a caller-supplied meta blob (parsed from the
  // IMPECCABLE_CRITIQUE_META env var) could clobber them, leaving the
  // filename in disagreement with its frontmatter and corrupting trends.
  const front = serializeFrontmatter({ ...meta, timestamp, slug });
  fs.writeFileSync(filePath, `${front}\n${body.trim()}\n`, 'utf-8');
  return filePath;
}

function serializeFrontmatter(obj) {
  const lines = ['---'];
  for (const [key, value] of Object.entries(obj)) {
    if (value === undefined || value === null) continue;
    const str = typeof value === 'string' ? value : String(value);

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Pass a non-empty slug string, e.g. writeSnapshot({ slug: 'hero-redesign', meta, body }).
  2. If slug is derived, resolve and validate it before calling writeSnapshot (fall back to a stable id or skip the write with a warning).
  3. Confirm the caller is forwarding the correct field name from its source object.

Example fix

// before
writeSnapshot({ meta, body }); // slug omitted -> throws

// after
writeSnapshot({ slug: 'hero-redesign', meta, body });
Defensive patterns

Strategy: validation

Validate before calling

function requireSlug(slug) {
  if (typeof slug !== 'string' || slug.trim() === '') {
    throw new Error('snapshot slug is required');
  }
  return slug;
}
// before calling writeSnapshot:
writeSnapshot({ slug: requireSlug(maybeSlug), meta, body });

Type guard

function isNonEmptyString(v) {
  return typeof v === 'string' && v.trim().length > 0;
}

if (!isNonEmptyString(slug)) {
  // skip or assign a stable fallback id
}

Prevention

When it happens

Trigger: Calling writeSnapshot({ meta, body }) with the slug key omitted, set to null/undefined, or passed as an empty string. Also reached when slug is derived upstream from a field that is absent (e.g. a design key or page URL that was never resolved) and the caller forwards undefined without checking.

Common situations: Programmatic batch writes where some entries lack a slug; a caller that reads slug from an env var (IMPECCABLE_CRITIQUE_META) or CLI arg that was not supplied; refactors that rename the slug source field but forget the writeSnapshot call site.

Related errors


AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13). Data as JSON: /api/errors/66f5d28ee5bd47fb. Report an issue: GitHub.