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
- Pass a non-empty slug string, e.g. writeSnapshot({ slug: 'hero-redesign', meta, body }).
- If slug is derived, resolve and validate it before calling writeSnapshot (fall back to a stable id or skip the write with a warning).
- 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
- Treat slug as a required field in the calling code's own type/contract, not an optional one.
- If slug is derived from another value, resolve and validate it at the boundary before forwarding.
- In batch loops, filter or skip entries lacking a slug and log them rather than letting writeSnapshot throw.
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
- Svelte-component source file not found: ${sourceFile}
- invalid catalog: ${validation.errors.join('; ')}
- concept-seed: --scope must be direction or surface
- concept-seed: --reroll must be a non-negative integer
- concept-seed: --register must be safer or bolder
AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13).
Data as JSON: /api/errors/66f5d28ee5bd47fb.
Report an issue: GitHub.