jackwener/OpenCLI · error · CommandExecutionError

expected array of rows from server, got ${typeof rows} (cont

Error message

expected array of rows from server, got ${typeof rows} (contract drift?)

What it means

This CommandExecutionError is thrown when dispatchEvaluateResult returns something that is not an array of bookmark rows. The bookmark-list command's contract says the /channels/saved response must dispatch to an array; anything else means the server response shape changed (contract drift) or the envelope was misparsed.

Source

Thrown at clis/slock/bookmark-list.js:41

  columns: ['id', 'messageId', 'content', 'savedAt'],
  func: async (page, kwargs) => {
    const limit = parsePositiveInteger(kwargs.limit, '--limit', { defaultValue: 50 });
    const offset = parseNonNegativeInteger(kwargs.offset, '--offset', { defaultValue: 0 });
    await page.goto(SLOCK_HOME_URL);
    const snippet = `
      ${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}
      const res = await fetch('${SLOCK_API_BASE}/channels/saved?limit=' + encodeURIComponent(${JSON.stringify(limit)}) + '&offset=' + encodeURIComponent(${JSON.stringify(offset)}), { credentials:'include', headers });
      if (!res.ok) return { kind: res.status===401?'auth':'http', status: res.status, where:'/channels/saved' };
      const data = await res.json();
      // F3-b — qatester live dump: shape is { saved: [...], hasMore }.
      // Unwrap .saved first; fall back to legacy .bookmarks / .data /
      // bare array for forward-compat.
      return { kind: 'ok', rows: Array.isArray(data) ? data : (data.saved || data.bookmarks || data.data || []) };
    `;
    const result = await page.evaluate(`(async () => { ${snippet} })()`);
    const rows = dispatchEvaluateResult(result);
    if (!Array.isArray(rows)) {
      throw new CommandExecutionError(`expected array of rows from server, got ${typeof rows} (contract drift?)`);
    }
    return rows.map((b) => ({
      id: b.id ?? '',
      messageId: b.messageId ?? '',
      content: b.content ?? b.message?.content ?? '',
      savedAt: b.savedAt ?? b.createdAt ?? '',
    }));
  },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw result before dispatch to see the actual response shape from /channels/saved.
  2. Upgrade or downgrade the CLI so its parsing matches your server's API version.
  3. If the server renamed the collection field, patch the snippet's fallback chain (add the new key).
  4. Retry after confirming you're hitting the right server (serverScoped override) — some deployments have divergent shapes.
Defensive patterns

Strategy: type-guard

Type guard

const isRowArray = (v) => Array.isArray(v) && v.every((x) => x != null && typeof x === 'object');

Try / catch

try {
  const bookmarks = await bookmarkList(page);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('contract drift')) {
    console.error('Response shape changed — check server/CLI version match:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: The server returns an object for the saved-list endpoint that dispatchEvaluateResult maps to a non-array (e.g. a pagination envelope {items, total}), or the snippet's fallback extraction (data.saved || data.bookmarks || data.data || []) misses the new key.

Common situations: A Slock server upgrade introducing a paginated/renamed response, a proxy injecting an HTML error page that parses oddly, or an older server whose field names differ from all the fallbacks.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/e08a5ec7288b872b. Report an issue: GitHub.