pydantic/monty · error · Error
snapshot has already been resumed
Error message
snapshot has already been resumed
What it means
The `SingleUse` class marks each snapshot as resumable at most once. `claim()` throws this Error when code attempts to resume a snapshot that has already been consumed, preventing double-resume of a paused worker turn. Snapshots are single-use by design: the underlying worker state can only advance once per captured point.
Source
Thrown at crates/monty-js/ts/session.ts:923
(await this.native.resumeNameLookup(null, { value: prepare(value, this.instances) }, this.onPrint)) as NativeTurn,
)
}
async resolveFutures(results: NativeFutureResult[]): Promise<Snapshot> {
return this.advance((await this.native.resolveFutures(results, this.onPrint)) as NativeTurn)
}
async dump(): Promise<Buffer> {
return bufferFrom(await this.native.dump())
}
}
/** Marks a snapshot single-use: each may be resumed at most once. */
class SingleUse {
private used = false
protected claim(): void {
if (this.used) {
throw new Error('snapshot has already been resumed')
}
this.used = true
}
}
/**
* A paused execution waiting for an external function or OS call result. For
* OS calls `isOsFunction` is `true`; resume with a value, an error, or
* `resumeNotHandled()`.
*/
export class FunctionSnapshot extends SingleUse {
readonly functionName: string
/** Positional arguments, already converted to JS values. */
readonly args: unknown[]
/** Keyword arguments (null-prototype record; string keys only). */
readonly kwargs: Record<string, unknown>
readonly callId: number
readonly isOsFunction: booleanView on GitHub (pinned to adc986b362)
Solutions
- Resume each Snapshot exactly once; capture a fresh snapshot for every attempt by re-running or re-loading.
- Track claimed state in your own code (e.g. a Set of consumed snapshots) before calling resume.
- If you need to retry with a different resume value, reload the snapshot via `session.loadSnapshot` to obtain a fresh, unclaimed instance if supported, or re-feed the code.
Example fix
// before
const snap = await session.resumeNotHandled()
await snap.resumeReturn(value)
await snap.resumeReturn(value) // throws
// after
const snap = await session.resumeNotHandled()
if (!consumed.has(snap)) {
consumed.add(snap)
await snap.resumeReturn(value)
} Defensive patterns
Strategy: validation
Validate before calling
function canResume(snap: Snapshot, consumed: WeakSet<Snapshot>): boolean {
return !consumed.has(snap)
} Try / catch
try {
await snap.resumeReturn(value)
} catch (err) {
if (err instanceof Error && err.message === 'snapshot has already been resumed') {
// obtain a fresh snapshot or skip
} else {
throw err
}
} Prevention
- Treat Snapshot objects as single-use: resume once, then discard the reference.
- Keep a WeakSet of consumed snapshots in any retry or multiplexing logic.
- Never share one snapshot across concurrent async tasks without ownership transfer.
When it happens
Trigger: Calling resumeReturn/resumeError/resumeNotHandled/resumeFuture (or any Snapshot resume method) twice on the same Snapshot object; storing a snapshot and re-resuming it after a previous resume already claimed it.
Common situations: Retry logic that captures a snapshot, resumes it, catches an error in the host callback, then tries to resume the same snapshot again with a different answer; sharing a snapshot across concurrent async tasks that both attempt resume.
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
- resumeNotHandled is only valid for OS-call snapshots
- ClassInstance expects an object instance
- notCallableMessage(method)
- ClassInstance expects an instance of a class, not a null-pro
- classType does not match the instance's class
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/eeed097da81f1742.
Report an issue: GitHub.