{"record":{"id":"8cb16a870b94d067","repo":"paperclipai/paperclip","slug":"provider-drain-state-is-malformed","errorCode":null,"errorMessage":"Provider drain state is malformed.","messagePattern":"Provider drain state is malformed\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/runnerd-codex-transport.ts","lineNumber":530,"sourceCode":"\nfunction providerDrainStateFromSnapshot(state: Record<string, unknown>): {\n  pendingEventCount: number;\n  activeProviderTurnId: string | null;\n  providerSettled: boolean;\n} {\n  if (\n    !Array.isArray(state.pendingEvents) ||\n    (state.queuedEvents !== undefined && !Array.isArray(state.queuedEvents)) ||\n    [state.activeProviderTurnId, state.activeTurnId].some(\n      (value) =>\n        value !== undefined &&\n        value !== null &&\n        (typeof value !== \"string\" || value.length === 0),\n    ) ||\n    (state.ambiguousTurnStartPending !== undefined &&\n      typeof state.ambiguousTurnStartPending !== \"boolean\")\n  )\n    throw new Error(\"Provider drain state is malformed.\");\n  const pending = Array.isArray(state.pendingEvents)\n    ? state.pendingEvents.length\n    : 0;\n  const queued = Array.isArray(state.queuedEvents)\n    ? state.queuedEvents.length\n    : 0;\n  const activeProviderTurnId =\n    [state.activeProviderTurnId, state.activeTurnId].find(\n      (value): value is string => typeof value === \"string\" && value.length > 0,\n    ) ?? null;\n  return {\n    pendingEventCount: pending + queued,\n    activeProviderTurnId,\n    providerSettled:\n      activeProviderTurnId === null && state.ambiguousTurnStartPending !== true,\n  };\n}\n","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/runnerd-codex-transport.ts#L512-L548","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst state = providerDrainStateFromSnapshot(rawSnapshot); // throws on malformed shape\n// after\nif (!Array.isArray(rawSnapshot.pendingEvents) || !Array.isArray(rawSnapshot.queuedEvents)) {\n  rawSnapshot = { pendingEvents: [], queuedEvents: [] }; // safe default\n}\nconst state = providerDrainStateFromSnapshot(rawSnapshot);","handlingStrategy":"type-guard","validationCode":"const isWellFormedDrainSnapshot = (s: unknown): boolean =>\n  typeof s === \"object\" && s !== null &&\n  (s.pendingEvents === undefined || Array.isArray(s.pendingEvents)) &&\n  (s.queuedEvents === undefined || Array.isArray(s.queuedEvents)) &&\n  (s.ambiguousTurnStartPending === undefined || typeof s.ambiguousTurnStartPending === \"boolean\");","typeGuard":"function isDrainSnapshot(v: unknown): v is ProviderDrainSnapshot {\n  return typeof v === \"object\" && v !== null &&\n    (v.pendingEvents === undefined || Array.isArray(v.pendingEvents)) &&\n    (v.queuedEvents === undefined || Array.isArray(v.queuedEvents)) &&\n    (v.ambiguousTurnStartPending === undefined || typeof v.ambiguousTurnStartPending === \"boolean\");\n}","tryCatchPattern":"let drainState: ProviderDrainState;\ntry {\n  drainState = providerDrainStateFromSnapshot(raw);\n} catch (err) {\n  if ((err as Error).message === \"Provider drain state is malformed.\") {\n    drainState = providerDrainStateFromSnapshot({ pendingEvents: [], queuedEvents: [] });\n  } else throw err;\n}","preventionTips":["Validate snapshot shape before persisting it","Migrate old snapshots when upgrading runner versions","Detect and repair truncated state files after crashes"],"tags":["validation","state","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}