{"record":{"id":"c40e9aa32bd3114a","repo":"jackwener/OpenCLI","slug":"expected-array-of-rows-from-server-got-typeof-r-c40e9a","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/channel-list.js","lineNumber":32,"sourceCode":"  strategy: Strategy.COOKIE,\n  browser: true,\n  siteSession: 'persistent',\n  args: [\n    { name: 'server', help: 'Override active server (slug or id) for this call' },\n  ],\n  columns: ['id', 'name', 'topic'],\n  func: async (page, kwargs) => {\n    await page.goto(SLOCK_HOME_URL);\n    const snippet = buildFetchSnippet({\n      method: 'GET',\n      path: '/channels/',\n      serverScoped: true,\n      serverIdOverride: kwargs.server,\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((c) => ({\n      id: c.id ?? '',\n      name: c.name ?? c.slug ?? '',\n      topic: c.topic ?? '',\n    }));\n  },\n});\n","sourceCodeStart":14,"sourceCodeEnd":41,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/channel-list.js#L14-L41","documentation":"After evaluating the server-scoped fetch snippet, `channel-list` expects `dispatchEvaluateResult` to return an array of channel rows and throws CommandExecutionError otherwise. '(contract drift?)' signals the server/CLI response contract changed — e.g. the endpoint now returns `{ channels: [...] }` or an error object.","triggerScenarios":"The evaluated snippet returns a non-array — Slock returns a wrapped object (`{ channels: [...] }`, `{ data: [...] }`), an auth/login HTML payload, or an error envelope the CLI doesn't unwrap.","commonSituations":"Slock app update altering the channels endpoint shape, expired session causing a redirect/error body, or using an old CLI against a newer Slock instance.","solutions":["Re-authenticate in the Slock app / refresh session and retry","Update the slock CLI to the version matching the current Slock app","Inspect the raw evaluate result with debug output to see the actual shape","Patch normalization locally (e.g. unwrap `data.channels`) and report upstream"],"exampleFix":"// before (in CLI)\nconst rows = dispatchEvaluateResult(result);\nif (!Array.isArray(rows)) throw new CommandExecutionError(...);\n// after (defensive unwrap)\nlet rows = dispatchEvaluateResult(result);\nif (!Array.isArray(rows) && Array.isArray(rows?.channels)) rows = rows.channels;","handlingStrategy":"type-guard","validationCode":"// cannot be validated pre-call; capture the raw response when debugging\n// add debug logging around page.evaluate to see what the server returned","typeGuard":"const isRowsPayload = (d) => Array.isArray(d) || (d && typeof d === 'object' && Array.isArray(d.channels));","tryCatchPattern":"try {\n  const channels = await slock.channelList();\n} catch (e) {\n  if (e instanceof CommandExecutionError && /contract drift/.test(e.message)) {\n    await refreshSession();\n    return retryWithUpdatedCli();\n  }\n  throw e;\n}","preventionTips":["Keep the CLI updated alongside Slock app changes","Re-authenticate when responses look like login/redirect payloads","Add contract tests that assert channelList returns an array"],"tags":["contract-drift","response-shape","cli","runtime-error"],"backgroundTag":"api-contract-drift","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}