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 dbOrTxView on GitHub (pinned to 01ad858492)
Solutions
- Retry the operation — the loop exits once issue context stops changing
- Identify and serialize the writer that repeatedly mutates the run's issue context
- Ensure only one dispatch/cancel worker operates per run (idempotent job claim)
- 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
- Serialize per-run operations with a job lock so only one worker dispatches/cancels at a time
- Avoid reassigning a run's issue while dispatch is in flight
- Use idempotent dispatch jobs with claim semantics
- Space out watchdog and dispatch schedules to reduce lock contention
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
- Cannot seed target embedded PostgreSQL at ${dataDir} while i
- Worktree seed source diagnostics changed while waiting for t
- Managed Codex credential ownership was lost
- Failed to start worktree port reservation lock heartbeat at
- Timed out waiting for worktree port reservation lock at ${lo
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/cd2857518ff6125b.
Report an issue: GitHub.