paperclipai/paperclip · error
Provider drain state is malformed.
Error message
Provider drain state is malformed.
What it means
providerDrainStateFromSnapshot converts a persisted provider drain snapshot into typed state and first validates its shape. It rejects snapshots whose pendingEvents/queuedEvents are not arrays, whose other fields are non-string-empty/null in unexpected ways, or whose ambiguousTurnStartPending is defined but not boolean. The error signals that the durable drain state cannot be trusted for event draining decisions.
Solutions
- Delete or regenerate the malformed drain-state snapshot so the runner rebuilds it from a clean state
- Check for runner version upgrades and migrate old snapshots to the current schema
- Inspect the state file for truncation/corruption and restore from backup if available
- Add validation at write time (assert shape before persisting) to prevent future malformed snapshots
Example fix
// before
const state = providerDrainStateFromSnapshot(rawSnapshot); // throws on malformed shape
// after
if (!Array.isArray(rawSnapshot.pendingEvents) || !Array.isArray(rawSnapshot.queuedEvents)) {
rawSnapshot = { pendingEvents: [], queuedEvents: [] }; // safe default
}
const state = providerDrainStateFromSnapshot(rawSnapshot); Defensive patterns
Strategy: type-guard
Validate before calling
const isWellFormedDrainSnapshot = (s: unknown): boolean => typeof s === "object" && s !== null && (s.pendingEvents === undefined || Array.isArray(s.pendingEvents)) && (s.queuedEvents === undefined || Array.isArray(s.queuedEvents)) && (s.ambiguousTurnStartPending === undefined || typeof s.ambiguousTurnStartPending === "boolean");
Type guard
function isDrainSnapshot(v: unknown): v is ProviderDrainSnapshot {
return typeof v === "object" && v !== null &&
(v.pendingEvents === undefined || Array.isArray(v.pendingEvents)) &&
(v.queuedEvents === undefined || Array.isArray(v.queuedEvents)) &&
(v.ambiguousTurnStartPending === undefined || typeof v.ambiguousTurnStartPending === "boolean");
} Try / catch
let drainState: ProviderDrainState;
try {
drainState = providerDrainStateFromSnapshot(raw);
} catch (err) {
if ((err as Error).message === "Provider drain state is malformed.") {
drainState = providerDrainStateFromSnapshot({ pendingEvents: [], queuedEvents: [] });
} else throw err;
} Prevention
- Validate snapshot shape before persisting it
- Migrate old snapshots when upgrading runner versions
- Detect and repair truncated state files after crashes
When it happens
Trigger: Calling providerDrainStateFromSnapshot (via provider or #providerDrainState) with a snapshot where: pendingEvents or queuedEvents is present but not an array, fields have empty-string/null/invalid values, or ambiguousTurnStartPending is set to a non-boolean value.
Common situations: Hand-edited or corrupted state files in the runner root; schema drift after upgrading the runner so old snapshots no longer match the expected shape; partial writes from a crash leaving truncated JSON-derived state; snapshots produced by a different runner version.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- does not carry submitted form data
- : invalid AppDefinition
- Durable PRP run attachment template is invalid.
- invalid question response
- Invalid semantic result
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-10).
Data as JSON: /api/errors/8cb16a870b94d067.
Report an issue: GitHub.
Appendix: source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:531
function providerDrainStateFromSnapshot(state: Record<string, unknown>): {
pendingEventCount: number;
activeProviderTurnId: string | null;
providerSettled: boolean;
} {
if (
!Array.isArray(state.pendingEvents) ||
(state.queuedEvents !== undefined && !Array.isArray(state.queuedEvents)) ||
[state.activeProviderTurnId, state.activeTurnId].some(
(value) =>
value !== undefined &&
value !== null &&
(typeof value !== "string" || value.length === 0),
) ||
(state.ambiguousTurnStartPending !== undefined &&
typeof state.ambiguousTurnStartPending !== "boolean")
)
throw new Error("Provider drain state is malformed.");
const pending = Array.isArray(state.pendingEvents)
? state.pendingEvents.length
: 0;
const queued = Array.isArray(state.queuedEvents)
? state.queuedEvents.length
: 0;
const activeProviderTurnId =
[state.activeProviderTurnId, state.activeTurnId].find(
(value): value is string => typeof value === "string" && value.length > 0,
) ?? null;
return {
pendingEventCount: pending + queued,
activeProviderTurnId,
providerSettled:
activeProviderTurnId === null && state.ambiguousTurnStartPending !== true,
};
}
View on GitHub (pinned to 3f1d897a7c)