affaan-m/ECC · error · SyntaxError

Malformed coordination JSON in body

Error message

Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}

What it means

extractCoordinationState finds an embedded coordination JSON block in an issue/PR body and throws a SyntaxError when that block fails JSON.parse. The message includes the underlying parse error plus the first 120 characters of the raw block to help locate the corruption. The library throws rather than silently ignoring the block because the coordination state is authoritative for claim/conflict decisions.

Solutions

  1. Read the 'raw:' excerpt in the message to find the exact JSON syntax error.
  2. Fix the coordination JSON block in the issue/PR body (validate with a JSON linter before saving).
  3. Restore the block by re-running the command that originally wrote the coordination state.
  4. If the block is unrecoverable, remove it and let the library re-initialize fresh coordination state.

Example fix

// before (in issue body)
<!-- ecc:coordination -->{"status": "claimed",, "owner": "alice"}
// after
<!-- ecc:coordination -->{"status": "claimed", "owner": "alice"}
Defensive patterns

Strategy: validation

Validate before calling

function isParseableJson(s) {
  try { JSON.parse(s); return true; } catch { return false; }
}
const block = body.match(/<!--\s*ecc:coordination\s*-->([\s\S]*?)<!--\s*\/ecc:coordination\s*-->/);
if (block && !isParseableJson(block[1])) throw new Error('Coordination block JSON is malformed; fix the issue body');

Type guard

const isCoordinationState = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.status === 'string';

Try / catch

let state;
try {
  state = extractCoordinationState(body);
} catch (e) {
  if (e instanceof SyntaxError && e.message.startsWith('Malformed coordination JSON')) {
    console.error('Fix the coordination JSON block in the issue body:', e.message);
    state = null;
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling getCoordinationState (or functions that read state from a body) when the fenced coordination JSON in the body was hand-edited, truncated, contains trailing commas/comments, or was mangled by another tool.

Common situations: Manual edits to the issue body that break the JSON (missing brace/quote), markdown formatters or GitHub UI edits reflowing the block, automation writing partial state before crashing, or copying the block from another issue with encoding damage.

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/dccb2e87f1287efe. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/github-coordination/parsing.js:26

function normalizeBodyForComparison(body) {
  return (body || '').replace(/"lastSyncAt"\s*:\s*[^,}\n]+/g, '"lastSyncAt": NORMALIZED');
}

function extractCoordinationState(body, policy = DEFAULT_POLICY) {
  const marker = escapeRegExp(policy.sectionMarker || DEFAULT_SECTION_MARKER);
  const regex = new RegExp(`<!--\\s*${marker}:start\\s*-->\\s*` + '```json\\s*([\\s\\S]*?)\\s*```' + `\\s*<!--\\s*${marker}:end\\s*-->`, 'm');
  const match = String(body || '').match(regex);

  if (!match) {
    return null;
  }

  try {
    const parsed = JSON.parse(match[1]);
    return parsed && typeof parsed === 'object' ? parsed : null;
  } catch (error) {
    throw new SyntaxError(`Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)}`);
  }
}

function extractIssueReferences(text) {
  const refs = new Set();
  const source = String(text || '');
  for (const match of source.matchAll(/(?:^|[^\d])#(\d+)\b/g)) {
    refs.add(Number.parseInt(match[1], 10));
  }
  return Array.from(refs)
    .filter(Number.isFinite)
    .sort((a, b) => a - b);
}

function extractTasks(body) {
  const lines = String(body || '').split(/\r?\n/);
  const tasks = [];
  let inTasks = false;

View on GitHub (pinned to 8321021c54)