affaan-m/ECC · warning

! : invalid meta.json, continuing with defaults ( )

Error message

  ! ${contextDir}: invalid meta.json, continuing with defaults (${e.message})

What it means

This is not a thrown error but a warning emitted by the CK v1→v2 context migration script in skills/ck/commands/migrate.mjs:118. When a context directory contains a meta.json file that fails JSON.parse (e.g. truncated, hand-edited, or corrupted file), the migration keeps going with an empty `meta` object ({}) so defaults are used for session IDs, dates, and summaries. The migration is deliberately non-fatal: losing v1 metadata is preferable to blocking the whole upgrade.

Solutions

  1. Validate the meta.json with `node -e "JSON.parse(require('fs').readFileSync('<path>','utf8'))"` to see the exact parse error and line.
  2. Fix the JSON syntax (or restore the file from git/backup), then re-run the migrate command.
  3. If the metadata is not needed, delete meta.json and let the migration proceed with defaults — the warning only means defaults will be substituted.
  4. Check for a BOM or encoding issue: `file meta.json` and re-save as plain UTF-8 without BOM.

Example fix

// before (broken meta.json)
{ "lastSessionId": 'abc123', }

// after
{ "lastSessionId": "abc123" }
Defensive patterns

Strategy: fallback

Validate before calling

const fs = require('fs');
let meta = {};
const raw = fs.existsSync(metaPath) ? fs.readFileSync(metaPath, 'utf8') : '';
if (raw) { try { JSON.parse(raw); } catch (e) { console.warn(`meta.json invalid: ${e.message}`); } }

Type guard

function isValidMeta(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); }

Try / catch

let meta = {};
try { meta = JSON.parse(readFileSync(metaPath, 'utf8')); }
catch (e) { console.warn(`invalid meta.json, using defaults: ${e.message}`); }

Prevention

When it happens

Trigger: Running the CK migrate command against a context directory whose meta.json is syntactically invalid JSON — trailing commas, single quotes, BOM bytes, a partially written file, or comments. JSON.parse throws and the catch block prints the warning with the parse error message.

Common situations: Manual edits to meta.json that broke syntax; an editor or sync tool truncated the file mid-write; a script appended non-JSON lines; a crash during a previous CK session left a zero-byte or half-written meta.json.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/ab744776b631fe41. Report an issue: GitHub.

Appendix: source

Thrown at skills/ck/commands/migrate.mjs:118

      if (existing.version === 2) {
        console.log(`  ✓ ${contextDir} — already v2, skipping`);
        skipped++;
        continue;
      }
    } catch { /* fall through to migrate */ }
  }

  console.log(`\n  → Migrating: ${contextDir}`);

  try {
    // Read v1 files
    const contextMd = existsSync(contextMdPath) ? readFileSync(contextMdPath, 'utf8') : '';
    let meta = {};
    if (existsSync(metaPath)) {
      try {
        meta = JSON.parse(readFileSync(metaPath, 'utf8'));
      } catch (e) {
        console.warn(`  ! ${contextDir}: invalid meta.json, continuing with defaults (${e.message})`);
      }
    }

    // Extract fields from CONTEXT.md
    const description   = extractSection(contextMd, 'What This Is') || extractSection(contextMd, 'About') || null;
    const stackRaw      = extractSection(contextMd, 'Tech Stack') || '';
    const stack         = stackRaw.split(/[,\n]/).map(s => s.replace(/^[-*]\s+/, '').trim()).filter(Boolean);
    const goal          = (extractSection(contextMd, 'Current Goal') || '').split('\n')[0].trim() || null;
    const constraintRaw = extractSection(contextMd, 'Do Not Do') || '';
    const constraints   = parseBullets(constraintRaw);
    const decisionsRaw  = extractSection(contextMd, 'Decisions Made') || '';
    const decisions     = parseDecisionsTable(decisionsRaw);
    const nextStepsRaw  = extractSection(contextMd, 'Next Steps') || '';
    const nextSteps     = parseBullets(nextStepsRaw);
    const blockersRaw   = extractSection(contextMd, 'Blockers') || '';
    const blockers      = parseBullets(blockersRaw).filter(b => b.toLowerCase() !== 'none');
    const leftOffRaw    = extractSection(contextMd, 'Where I Left Off') || '';
    const leftOffParsed = parseLeftOff(leftOffRaw);

View on GitHub (pinned to 8321021c54)