affaan-m/ECC · error

Metadata reorder detected at

Error message

Metadata reorder detected at ${location}; move the matching sidecar entry before refreshing fingerprints

What it means

withRefreshedFingerprints() in scripts/lib/hooks-config.js cross-checks the order of hook entries in hooks.json against fingerprint positions recorded in the metadata sidecar. If an entry's fingerprint exists in the sidecar but at a different location (event[index]) than its current position, it throws a 'Metadata reorder detected' error. This is an integrity guard: fingerprints are position-sensitive, so a silent reorder would attach metadata (matchers, descriptions) to the wrong hook.

Solutions

  1. Reorder the entries in the metadata sidecar to mirror the new order in hooks.json (the error names the exact location, e.g. Stop[2]).
  2. Alternatively revert hooks.json to the order recorded in the sidecar, then re-apply changes via the ECC tooling (addJsonMergeOperation) so sidecar and hooks stay in sync.
  3. If the reorder is intentional and metadata mapping is stale, regenerate the sidecar metadata for the new order rather than refreshing fingerprints in place.

Example fix

// before: hooks.json has matcher A,B but sidecar lists fingerprints as [B,A]
withRefreshedFingerprints(...); // throws Metadata reorder detected at Stop[1]
// after: reorder sidecar entries array to [fingerprintA, fingerprintB]
metadata.entries.Stop = [fpA, fpB]; // match hooks.json order
withRefreshedFingerprints(...); // ok
Defensive patterns

Strategy: validation

Validate before calling

// verify hooks.json entry order matches sidecar metadata before refreshing
const cfg = JSON.parse(fs.readFileSync(hooksPath, 'utf8'));
const meta = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
for (const [event, entries] of Object.entries(cfg.hooks || {})) {
  const fps = (meta.entries?.[event] || []).map(e => e.fingerprint);
  const current = entries.map(e => fingerprintFor(e));
  const aligned = current.every((fp, i) => fp === fps[i]);
  if (!aligned) throw new Error(`Reorder ${event} sidecar entries before refreshing fingerprints`);
}

Type guard

function isAlignedOrder(entries, metadataEntries) {
  return Array.isArray(entries) && Array.isArray(metadataEntries) &&
    entries.length === metadataEntries.length;
}

Try / catch

try {
  const result = withRefreshedFingerprints(/* ... */);
} catch (err) {
  if (String(err.message).startsWith('Metadata reorder detected')) {
    const loc = err.message.match(/at (\S+);/)?.[1];
    console.error(`Reorder sidecar entry for ${loc} to match hooks.json, then retry.`);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling validateHooks() or refreshed() after manually reordering, inserting, or deleting entries within a hooks event array in hooks.json without moving the corresponding entries in the metadata sidecar (.hooks-metadata style file) to match.

Common situations: Hand-editing hooks.json to reorder matchers; a formatter or merge that reshuffled array entries; deleting an earlier hook so subsequent indexes shift while sidecar entries keep old indexes.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at scripts/lib/hooks-config.js:248

  // A known fingerprint at another position signals a reorder, not a command
  // edit. Require the author to move its metadata before refreshing anything.
  const positions = new Map();
  for (const [event, entries] of Object.entries(events)) {
    if (!Array.isArray(entries)) continue;
    entries.forEach((entry, index) => {
      const fingerprint = fingerprintHookEntry(entry);
      const locations = positions.get(fingerprint) || [];
      positions.set(fingerprint, [...locations, `${event}[${index}]`]);
    });
  }
  for (const [event, entries] of Object.entries(entriesByEvent)) {
    if (!Array.isArray(entries)) continue;
    entries.forEach((entry, index) => {
      const locations = positions.get(entry?.fingerprint);
      const location = `${event}[${index}]`;
      if (locations && !locations.includes(location)) {
        throw new Error(`Metadata reorder detected at ${location}; move the matching sidecar entry before refreshing fingerprints`);
      }
    });
  }

  for (const [event, eventMetadata] of Object.entries(entriesByEvent)) {
    const entries = Array.isArray(events[event]) ? events[event] : [];
    refreshed[event] = Array.isArray(eventMetadata)
      ? eventMetadata.map((entry, index) => (
        entry && typeof entry === 'object' && index < entries.length
          ? { ...entry, fingerprint: fingerprintHookEntry(entries[index]) }
          : entry
      ))
      : eventMetadata;
  }

  return { ...metadata, entries: refreshed };
}

View on GitHub (pinned to 8321021c54)