{"record":{"id":"86b9aea248575f0b","repo":"jackwener/OpenCLI","slug":"expected-array-of-rows-from-server-got-typeof-l","errorCode":null,"errorMessage":"expected array of rows from server, got ${typeof list} (contract drift?)","messagePattern":"expected array of rows from server, got (.+?) \\(contract drift\\?\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/slock/message-read.js","lineNumber":83,"sourceCode":"    const params = {\n      isUuid, channel, after, before, limit, noThreads, override,\n      parentTarget: tt?.parentTarget ?? '',\n      parentMsgId: tt?.parentMsgId ?? '',\n      isThread: !!tt,\n    };\n    const snippet = buildReadSnippet(params);\n    const result = await page.evaluate(`(async () => { ${snippet} })()`);\n\n    if (result?.kind === 'no-thread') {\n      return [{\n        id: '', seq: null, createdAt: '', senderName: '',\n        content: `(${result.parent} — no thread yet, 0 replies.)`,\n        threadChannelId: null, replyCount: 0, unreadCount: 0, lastReplyAt: null,\n      }];\n    }\n    const list = dispatchEvaluateResult(result);\n    if (!Array.isArray(list)) {\n      throw new CommandExecutionError(`expected array of rows from server, got ${typeof list} (contract drift?)`);\n    }\n    const threadsMap = result.meta?.threadsMap ?? {};\n    const threadsDegraded = result.meta?.threadsDegraded === true;\n    const mapArg = threadsDegraded ? null : threadsMap;\n    const rows = list.map((m) => mapRow(m, mapArg));\n    if (threadsDegraded) {\n      rows.unshift({\n        id: '', seq: null, createdAt: '', senderName: '',\n        content: '(threads-enrichment unavailable — replyCount/threadChannelId set to null. Retry to get reply counts.)',\n        threadChannelId: null, replyCount: null, unreadCount: null, lastReplyAt: null,\n      });\n    }\n    return rows;\n  },\n});\n\nfunction buildReadSnippet(p) {\n  const target = JSON.stringify(p.channel.replace(/^#/, '').toLowerCase());","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/message-read.js#L65-L101","documentation":"message-read.js expects the server evaluate result (after dispatchEvaluateResult) to be an array of thread/message rows so it can map each via mapRow. If the normalized result is not an array, it throws this CommandExecutionError indicating a server/CLI contract drift.","triggerScenarios":"Running message-read when the endpoint returns an object envelope (e.g. {messages: [...]}) or an error object instead of a bare array; a degraded threads enrichment path returning unexpected shape.","commonSituations":"Server upgrade changed the response shape; auth failure returned an error payload that leaked through dispatchEvaluateResult; middlewares/proxies wrapping the array.","solutions":["Update the CLI to the server's current response contract (check where the array is now nested).","Log the raw `result`/`list` before the throw to inspect the actual shape.","Confirm active server and auth are valid so the real payload is returned.","Check release notes for breaking API changes to the messages endpoint."],"exampleFix":"// before\nconst list = dispatchEvaluateResult(result);\n// after\nconst raw = dispatchEvaluateResult(result);\nconst list = Array.isArray(raw) ? raw : (raw.messages || raw.rows || []);","handlingStrategy":"type-guard","validationCode":"const list = dispatchEvaluateResult(result);\nif (!Array.isArray(list)) throw new Error(`unexpected message-read payload: ${JSON.stringify(list).slice(0,200)}`);","typeGuard":"function isRowArray(v) { return Array.isArray(v); }","tryCatchPattern":"try { const rows = await readMessages(page, kwargs); } catch (e) { if (String(e.message).includes('expected array of rows from server')) { console.error('Response contract drift — inspect raw result and update CLI'); } else throw e; }","preventionTips":["Keep CLI and server versions in lockstep","Add a debug flag that dumps dispatchEvaluateResult output","Handle error-object payloads inside dispatchEvaluateResult instead of letting them leak"],"tags":["contract-drift","response-shape","cli"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}