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.
Source
Thrown at packages/paperclip-runner/src/live/runnerd-codex-transport.ts:530
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 01ad858492)
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
- Worktree seed manifest is missing source path diagnostics.
- unsupported request schema
- Public attempt must contain the read-only public chat projec
- Unknown public chat payload field
- Unknown public chat projection field
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/8cb16a870b94d067.
Report an issue: GitHub.