{"record":{"id":"2377ac4e8ee20440","repo":"nanocoai/nanoclaw","slug":"malformed-outbound-row-delivering-best-effort","errorCode":null,"errorMessage":"Malformed outbound row — delivering best-effort","messagePattern":"Malformed outbound row — delivering best-effort","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/mailbox/sqlite/index.ts","lineNumber":367,"sourceCode":"                id: row.id,\n                sequence: row.seq,\n                inReplyTo: row.in_reply_to,\n                timestamp: sqliteTimestamp(row.timestamp),\n                deliverAfter: row.deliver_after === null ? null : sqliteTimestamp(row.deliver_after),\n                recurrence: row.recurrence,\n                kind: row.kind,\n                platformId: row.platform_id,\n                channelType: row.channel_type,\n                threadId: row.thread_id,\n                content: row.content,\n              }),\n            );\n          } catch (err) {\n            // One malformed row must not block the whole delivery queue: fall\n            // back to a best-effort read so the row goes through the normal\n            // per-message retry → mark-failed containment instead of throwing\n            // out of the entire drain on every poll.\n            log.warn('Malformed outbound row — delivering best-effort', { id: String(row.id), err });\n            const text = (value: unknown): string =>\n              typeof value === 'string' ? value : value == null ? '' : String(value);\n            const nullableText = (value: unknown): string | null => (value == null ? null : String(value));\n            return {\n              id: text(row.id),\n              kind: text(row.kind),\n              platformId: nullableText(row.platform_id),\n              channelType: nullableText(row.channel_type),\n              threadId: nullableText(row.thread_id),\n              content: text(row.content),\n              inReplyTo: nullableText(row.in_reply_to),\n            };\n          }\n        }),\n    writeDirect: async (message) => {\n      const writer = writable();\n      const sequence = nextSequence();\n      const record = createDirectOutboundRecord(message, sequence, new Date().toISOString());","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/mailbox/sqlite/index.ts#L349-L385","documentation":"A row in outbound.db failed strict parsing when reading pending messages. Instead of failing the whole drain, the wrapper coerces fields best-effort so the row flows through normal per-message retry/mark-failed containment. One bad row doesn't block delivery of the rest.","triggerScenarios":"A messages_out row has unexpected types (numbers in text columns, null in required fields) — from a buggy agent-runner version, disk corruption, or manual DB edits.","commonSituations":"Upgrading agent-runner with a schema drift, or someone editing outbound.db directly.","solutions":["Check the id in the log; inspect the row: sqlite queries against messages_out for that id","Mark it failed or delete it so it stops appearing: it will retry otherwise","Avoid writing to session DBs from outside host/container code","Rebuild/upgrade agent-runner if a version wrote bad rows"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// inspect suspicious rows before they clog the queue\nSELECT id, kind, typeof(text) FROM messages_out WHERE ok=0;","typeGuard":"function isWellFormedOutboundRow(row: unknown): boolean {\n  const r = row as Record<string, unknown>;\n  return typeof r.id === 'string' && typeof r.kind === 'string';\n}","tryCatchPattern":"try { parseStrict(row); } catch { deliverBestEffort(coerce(row)); /* goes through retry/mark-failed */ }","preventionTips":["Never write to session DBs outside host/agent-runner code","Keep agent-runner image and host versions in lockstep","Mark chronically malformed rows failed so drains stay clean"],"tags":["sqlite","outbound","schema-validation","delivery"],"backgroundTag":"malformed-database-row","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}