paperclipai/paperclip · error · Error

run-dispatch: run ${input.runId} changed issue context repea

Error message

run-dispatch: run ${input.runId} changed issue context repeatedly while acquiring locks

What it means

Raised by withIssueThenRunLocks in the run-dispatch Postgres adapter when it repeatedly fails to acquire issue+run locks because the run's issue context (issueId) kept changing between read attempts. It is an internal retry-exhaustion guard against livelock when issue context mutates concurrently.

Source

Thrown at server/src/modules/run-dispatch/adapters/postgres.ts:219

              and(eq(heartbeatRuns.id, input.runId), eq(heartbeatRuns.companyId, input.companyId)),
            )
            // Keep the run status stable through the semantic decision and any
            // resulting mutation and synchronous dispatch handoff. Never await
            // adapter-owned work while this transaction holds the row locks.
            .for("update")
            .then((rows) => rows[0] ?? null);
          if (!run) return { kind: "missing" as const };

          const lockedIssueId = readNonEmptyString(parseObject(run.contextSnapshot).issueId);
          if (lockedIssueId !== hintedIssueId) return { kind: "retry" as const };
          return { kind: "value" as const, value: await operation(typedTx, run) };
        });

      if (result.kind === "missing") return onMissing();
      if (result.kind === "value") return result.value;
    }

    throw new Error(
      `run-dispatch: run ${input.runId} changed issue context repeatedly while acquiring locks`,
    );
  }

  async function loadGateFacts(
    input: LoadGateFactsInput,
    now: Date,
    tx?: unknown,
  ): Promise<LoadGateFactsResult> {
    // Semantic adapter operations pass their transaction here so the fact
    // read and the state transition share one unit of work. This helper is
    // deliberately not exposed through the module's public API.
    const dbOrTx = (tx as Db | undefined) ?? db;
    const budgetsForRead = tx ? budgetService(dbOrTx) : budgets;
    const treeControlForRead = tx ? issueTreeControlService(dbOrTx) : treeControlSvc;
    const issuesSvcForRead = tx ? issueService(dbOrTx) : issuesSvc;

    const agent = await dbOrTx

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry the operation — the loop exits once issue context stops changing
  2. Identify and serialize the writer that repeatedly mutates the run's issue context
  3. Ensure only one dispatch/cancel worker operates per run (idempotent job claim)
  4. Reduce contention by spacing watchdog/dispatch schedules

Example fix

// before
await adapter.cancelStaleQueuedRun(input); // may throw after repeated context change
// after
try {
  await adapter.cancelStaleQueuedRun(input);
} catch (e) {
  if (e.message.includes('changed issue context repeatedly')) await delay(200), retry(input);
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// confirm issue context is stable before dispatching
const a = await getRunIssueContext(runId); await delay(50); const b = await getRunIssueContext(runId);
if (a !== b) await delay(500);

Try / catch

try { await op(input) } catch (e) { if (e.message.includes('changed issue context repeatedly')) { await backoff(); return op(input); } throw e; }

Prevention

When it happens

Trigger: Concurrent operations (cancelStaleQueuedRun, dispatchResolvedInteractionIfCurrent, transactionResult) racing with something that keeps re-pointing the run at a different issue, exhausting the internal retry loop.

Common situations: Two dispatch/cancel workers racing on the same run; a transaction moving the run between issues while a lock acquisition is in flight; retry storm under heavy scheduling contention.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/cd2857518ff6125b. Report an issue: GitHub.