{"record":{"id":"f8716a7db36eea36","repo":"jackwener/OpenCLI","slug":"expected-files-array-got-typeof-files-contrac","errorCode":null,"errorMessage":"expected files array, got ${typeof files} (contract drift?)","messagePattern":"expected files array, got (.+?) \\(contract drift\\?\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/slock/channel-files.js","lineNumber":40,"sourceCode":"  ],\n  columns: ['id', 'filename', 'mimeType', 'sizeBytes', 'messageId', 'createdAt'],\n  func: async (page, kwargs) => {\n    const channel = String(kwargs.channel ?? '').trim();\n    if (!channel) throw new ArgumentError('channel required');\n    const limit = parsePositiveInteger(kwargs.limit, '--limit', { defaultValue: 50 });\n    await page.goto(SLOCK_HOME_URL);\n    const snippet = buildChannelScopedSnippet({\n      channelInput: channel,\n      method: 'GET',\n      pathSuffix: '/files',\n      query: `?limit=${limit}`,\n      serverIdOverride: kwargs.server,\n    });\n    const result = await page.evaluate(`(async () => { ${snippet} })()`);\n    const data = dispatchEvaluateResult(result);\n    const files = Array.isArray(data) ? data : (data.files || []);\n    if (!Array.isArray(files)) {\n      throw new CommandExecutionError(`expected files array, got ${typeof files} (contract drift?)`);\n    }\n    return files.map((f) => ({\n      id: f.id ?? '',\n      filename: f.filename ?? '',\n      mimeType: f.mimeType ?? '',\n      sizeBytes: typeof f.sizeBytes === 'number' ? f.sizeBytes : null,\n      messageId: f.messageId ?? '',\n      createdAt: f.createdAt ?? '',\n    }));\n  },\n});\n","sourceCodeStart":22,"sourceCodeEnd":52,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/channel-files.js#L22-L52","documentation":"After evaluating the in-page fetch snippet, `channel-files` normalizes the result to a `files` array and throws CommandExecutionError if the result is neither an array nor an object with a `files` array. The '(contract drift?)' note flags that Slock's response shape changed from what the CLI expects.","triggerScenarios":"The evaluated snippet returns an object without a `files` array — e.g. Slock returns `{ data: {...} }`, an error envelope, or an HTML/login page parsed as an object instead of the expected `{ files: [...] }` or plain array.","commonSituations":"Slock app update changing the files endpoint response shape, session expiry causing the snippet to receive an auth redirect body, or a server returning an error object the CLI's `dispatchEvaluateResult` passes through.","solutions":["Re-authenticate / refresh the session in the Slock app, then retry","Update the slock CLI package to the latest version matching the current Slock app contract","Log the raw result (rerun with debug output) and inspect the actual response shape","File/check an issue against the CLI if the Slock API changed its files payload"],"exampleFix":"// before (in CLI)\nconst files = Array.isArray(data) ? data : (data.files || []);\n// after (defensive)\nconst files = Array.isArray(data) ? data : (Array.isArray(data.files) ? data.files : Array.isArray(data.data?.files) ? data.data.files : []);","handlingStrategy":"type-guard","validationCode":"// cannot be validated pre-call; inspect raw output when debugging\n// rerun with debug logging to capture the raw page.evaluate result shape","typeGuard":"const isFilesPayload = (d) => Array.isArray(d) || (d && typeof d === 'object' && Array.isArray(d.files));","tryCatchPattern":"try {\n  const files = await slock.channelFiles({ channel: '#ops' });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /contract drift/.test(e.message)) {\n    // refresh session, then upgrade the CLI to match the current Slock contract\n    await refreshSession();\n    return retryWithUpdatedCli();\n  }\n  throw e;\n}","preventionTips":["Keep the CLI version in sync with the Slock app version","Refresh the Slock session when payloads start looking like HTML/login pages","Pin and test CLI integrations against Slock updates; subscribe to changelogs"],"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"}