koala73/worldmonitor · error · Error

intelHistory.retract: no identifier resolved to a dedupeKey

Error message

intelHistory.retract: no identifier resolved to a dedupeKey — nothing was tombstoned. Unresolved ids: ${unresolvedIds.join(", ")}. A deleted or pruned row cannot be retracted by id; re-run with --dedupe-key to suppress the identity itself.

What it means

Error "intelHistory.retract: no identifier resolved to a dedupeKey — nothing was tombstoned. Unresolved ids: ${unresolvedIds.join(", ")}. A deleted or pruned row cannot be retracted by id; re-run with --dedupe-key to suppress the identity itself." thrown in koala73/worldmonitor.

Source

Thrown at convex/intelHistory.ts:477

    assertRetractionIdentifierBudget(args);
    const reason = args.reason.trim();
    if (!reason) {
      throw new Error("intelHistory.retract: reason is required");
    }

    await touchAppendLock(ctx);

    const { keys, unresolvedIds } = await resolveRetractionKeys(ctx, args);

    // Fail loudly rather than report a successful no-op. A document id is the
    // handle an operator copies out of a search result, and a row that was
    // already pruned — or already retracted — no longer resolves to one. With
    // no key to tombstone the loop below does nothing, and a 200 saying
    // `deleted: 0, tombstoned: 0` reads as "already clean" when the truth is
    // "nothing was suppressed and the next seed tick will re-add it". The
    // recovery is in the message because the operator needs it right there.
    if (keys.length === 0) {
      throw new Error(
        `intelHistory.retract: no identifier resolved to a dedupeKey — nothing was tombstoned. ` +
          `Unresolved ids: ${unresolvedIds.join(", ")}. A deleted or pruned row cannot be ` +
          `retracted by id; re-run with --dedupe-key to suppress the identity itself.`,
      );
    }

    const retractedAt = Date.now();

    let deleted = 0;
    let tombstoned = 0;
    let refreshed = 0;
    for (const dedupeKey of keys) {
      const [rows, existingTombstone] = await Promise.all([
        // `collect` over the dedupe index rather than `first`: the index is
        // meant to hold one row per key, and if an old bug ever put two there
        // a retraction that removed only one would leave the poisoned text
        // live while reporting success.
        ctx.db

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Re-run the retract with --dedupe-key to tombstone the identity itself when the row no longer resolves by document id
  2. If the row should still exist, verify the copied id — deleted or pruned rows cannot be retracted by id
  3. Treat the loud failure as intended: it prevents a silent no-op being mistaken for a successful retraction
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/intelHistory.ts:477 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/e58439d7e98dd504. Report an issue: GitHub.