{"record":{"id":"9efc39b7ec755c9d","repo":"jackwener/OpenCLI","slug":"expected-array-of-rows-from-server-got-typeof-r-9efc39","errorCode":null,"errorMessage":"expected array of rows from server, got ${typeof rows} (contract drift?)","messagePattern":"expected array of rows from server, got (.+?) \\(contract drift\\?\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/slock/message-search.js","lineNumber":62,"sourceCode":"          const arr = await cres.json();\n          const hit = (Array.isArray(arr)?arr:(arr.channels||arr.data||[])).find((c) => (c.name||c.slug||'').toLowerCase() === ${target});\n          if (!hit) return { kind: 'unresolvable', detail: 'no channel matches ' + ${JSON.stringify(channel)} };\n          channelId = hit.id;\n        }\n      }\n      const searchUrl = '${SLOCK_API_BASE}/messages/search?q=' + encodeURIComponent(${JSON.stringify(q)}) + (channelId ? '&channelId=' + encodeURIComponent(channelId) : '') + '&limit=' + encodeURIComponent(${JSON.stringify(limit)});\n      const res = await fetch(searchUrl, { credentials:'include', headers });\n      if (!res.ok) return { kind: res.status===401?'auth':'http', status: res.status, where:'/messages/search' };\n      const data = await res.json();\n      // F2-b — qatester live dump: shape is { results: [...], hasMore }.\n      // Unwrap .results first; fall back to legacy .messages / .data /\n      // bare array for forward-compat.\n      return { kind: 'ok', rows: Array.isArray(data) ? data : (data.results || data.messages || data.data || []) };\n    `;\n    const result = await page.evaluate(`(async () => { ${snippet} })()`);\n    const rows = dispatchEvaluateResult(result);\n    if (!Array.isArray(rows)) {\n      throw new CommandExecutionError(`expected array of rows from server, got ${typeof rows} (contract drift?)`);\n    }\n    return rows.map((m) => ({\n      id: m.id ?? m.messageId ?? '',\n      channelId: m.channelId ?? '',\n      createdAt: m.createdAt ?? m.created_at ?? '',\n      senderName: m.sender?.name ?? m.user?.name ?? '',\n      content: m.content ?? '',\n    }));\n  },\n});\n","sourceCodeStart":44,"sourceCodeEnd":73,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/message-search.js#L44-L73","documentation":"message-search.js expects the normalized evaluate result to be an array of message rows which it then maps to output columns. If dispatchEvaluateResult returns a non-array it throws this CommandExecutionError, signaling the server response no longer matches the CLI's expected contract (the snippet itself already coerces bare arrays or objects with results/messages/data).","triggerScenarios":"The endpoint returns an envelope field the snippet doesn't know (e.g. `hits`, `matches`); an auth/error object passes through dispatchEvaluateResult as a non-array; the snippet returns {kind:'error'} shape unexpectedly.","commonSituations":"Server API upgrade renaming the results field; proxy injecting error bodies; degraded mode returning metadata instead of rows.","solutions":["Extend the fallback chain in the snippet's return line to include the new field name (e.g. data.hits || data.matches).","Log `result`/`rows` before the throw to see the actual envelope.","Verify auth and active server so the search endpoint returns real rows.","Update the CLI to match the current server search contract."],"exampleFix":"// before\nreturn { kind: 'ok', rows: Array.isArray(data) ? data : (data.results || data.messages || data.data || []) };\n// after\nreturn { kind: 'ok', rows: Array.isArray(data) ? data : (data.results || data.messages || data.data || data.hits || []) };","handlingStrategy":"type-guard","validationCode":"const rows = dispatchEvaluateResult(result);\nif (!Array.isArray(rows)) throw new Error(`unexpected search payload: ${JSON.stringify(rows).slice(0,200)}`);","typeGuard":"function isSearchRows(v) { return Array.isArray(v); }","tryCatchPattern":"try { const rows = await searchMessages(page, kwargs); } catch (e) { if (String(e.message).includes('expected array of rows from server')) { console.error('Search response contract drift — check envelope field names'); } else throw e; }","preventionTips":["Extend the snippet's envelope fallback chain when the server adds new field names","Log raw results on first integration with a new server","Convert API error objects into thrown errors inside dispatchEvaluateResult"],"tags":["contract-drift","response-shape","cli"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}