.claude/skills/gray-matter-date-gotchas/SKILL.md
Handle gray-matter's automatic Date parsing in YAML frontmatter. Use when: (1) Zod validation fails with "expected string, received Date" on date fields, (2) Building content collection systems with markdown frontmatter, (3) Dates in frontmatter become Date objects instead of strings, (4) Migration/precompile scripts process YAML with date fields. Covers z.preprocess coercion and ensuring dates are written as strings.
npx skillsauth add Dbochman/dotfiles gray-matter-date-gotchasInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The gray-matter library automatically parses YAML date values as JavaScript Date objects. This breaks validation when using Zod schemas that expect strings, and can cause silent data corruption in migration pipelines.
frontmatter.createdAt instanceof Date returns true unexpectedlyYAML 1.1 (which gray-matter uses) auto-detects dates in these formats:
2026-01-23 (date only)2026-01-23T00:00:00.000Z (ISO 8601)Gray-matter parses these as Date objects, not strings.
Coerce Date objects to ISO strings before validation:
const isoDateString = z.preprocess(
(val) => {
if (val instanceof Date) return val.toISOString();
return val;
},
z.string().refine(
(val) => {
const fullIso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
const dateOnly = /^\d{4}-\d{2}-\d{2}$/;
return fullIso.test(val) || dateOnly.test(val);
},
{ message: 'Invalid date format. Expected ISO 8601' }
)
);
When writing frontmatter, explicitly convert dates:
function toISOString(value) {
if (!value) return value;
if (value instanceof Date) return value.toISOString();
if (typeof value === 'string') return value;
return String(value);
}
// When building frontmatter object:
frontmatter.createdAt = toISOString(card.createdAt) || new Date().toISOString();
Quoted strings aren't parsed as dates:
createdAt: "2026-01-23T00:00:00.000Z" # String
createdAt: 2026-01-23 # Becomes Date object!
const { data } = matter(content);
console.log(typeof data.createdAt); // Should be 'string'
console.log(data.createdAt); // Should be ISO string, not [object Date]
Before (breaks):
const schema = z.object({
createdAt: z.string() // Fails! gray-matter returns Date
});
After (works):
const schema = z.object({
createdAt: z.preprocess(
(val) => val instanceof Date ? val.toISOString() : val,
z.string()
)
});
tools
Use exact configured Reolink cameras through the local Home Hub for availability and power status, fresh stills, visual commentary, protected Dylan/Julia/household sharing, and reversible spotlight control. Supports trusted owner tasks and explicitly scoped proactive automations; not for Nest or Ring cameras, arbitrary recipients, recordings, account changes, or raw camera APIs.
data-ai
Privately manage Dylan and Julia's household plant inventory and care history by physical location, bed, and exact Flower Cam view. Use for confirmed plant onboarding from camera conversations, camera- or bed-filtered inventory, record corrections, individual or whole-bed care, and private filtered exports. Pair with reolink-camera when an owner asks about plants visible in Flower Cam images.
testing
Inspect and control the physically secured Reachy Mini at Crosstown through ClawBody. Use for requests to check Reachy, look around, express an emotion, play any official emotion or dance preset, speak proactively, mute or unmute its microphone, stop movement, or describe what its camera sees.
tools
Handle Reachy/iMessage handoffs, selective durable memory, forgetting, and diagnostics; automatic context comes from the gateway plugin.