Prune old runs and tokens
Remove terminal history in bounded batches without touching active work.
Pruning deletes data. Choose retention from your audit, support, replay, and compliance needs. Before you start, make sure backups or downstream exports retain anything that must outlive Runlane storage.
Only terminal runs older than the cutoff can be pruned. Active work is never a valid target.
Understand when a run disappears
Runlane first makes the run logically absent. One transaction tombstones it, removes operational and retained idempotency ownership, and blocks new outbox claims.
Point reads, events, steps, and operator views stop returning the run at this boundary.
Runlane removes child events, steps, outbox records, and token links in bounded transactions. Cleanup can restart after interruption.
prunedCount counts runs that became logically absent. Physical storage may fall later as maintenance finishes cleanup.
Run cleanup removes matching token links and updates their counters atomically. It does not delete the token.
A pruned run cannot keep a terminal token eligible for resume.
Observation records belong to a separate export stream. Export and checkpoint anything you need before its own retention policy removes it.
Preview the candidates
Use runs.list() or runlane runs list with the same terminal statuses and time bounds. Inspect a few candidates first. Start with a small prune limit and raise it only after you see the effect on the database.
Prune one batch
olderThan accepts a duration or a date. A duration becomes one fixed cutoff when the first page starts, so later pages keep the same scope.
Put the bounded loop in a scheduled job or operator script, such as scripts/prune-runlane-runs.ts:
import { type RunlaneRuntime } from '@runlane/core'
export async function pruneAllRuns(runlane: RunlaneRuntime) {
let cursor: string | undefined
let prunedCount = 0
do {
const result = await runlane.runs.prune({
cursor,
olderThan: '30d',
limit: 500,
})
prunedCount += result.prunedCount
cursor = result.nextCursor
} while (cursor !== undefined)
return prunedCount
}Continue from the returned cursor
Keep the cursor opaque. Reuse it only with the same environment, cutoff, and status filter. Save it outside the process when the job must survive a restart.
The cursor tracks terminal-run discovery. Maintenance resumes physical cleanup from durable tombstones after a process stops. A pruned run does not become visible again, but its child rows can remain stored until maintenance runs.
If Runlane rejects a cursor, restart with the intended filter. Do not edit or decode it, and do not loosen the cutoff to make the job look successful.
Prune terminal tokens separately
Tokens have their own retention boundary:
import { type RunlaneRuntime } from '@runlane/core'
export async function pruneOneTokenBatch(runlane: RunlaneRuntime) {
return runlane.wait.pruneTokens({
olderThan: '30d',
limit: 500,
})
}Only terminal tokens resolved before the cutoff and connected to zero runs can be deleted. The prune transaction removes the token and its exact idempotency owner together. Pending tokens, connected terminal tokens, and inconsistent owner pairs stay in place.
Continue with nextCursor as you do for runs. The first page fixes a duration cutoff using storage time and stores that scope in the cursor. Reuse it only with the same environment and olderThan input.
Choose run and token retention together. Pruning a terminal run may need to remove links before its token becomes eligible. Pending application reviews remain outside both terminal prune sets.