jackwener/OpenCLI · error · CommandExecutionError

expected array of summary rows, got

Error message

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

What it means

unread-summary dispatches the page.evaluate result and expects an array of summary rows; if the result is not an array it throws this CommandExecutionError labeling it contract drift. Like thread-list, it refuses to proceed when the response shape doesn't match the documented rows contract.

Solutions

  1. Print the raw result and typeof to see the actual envelope.
  2. Unwrap the envelope in the snippet or dispatchEvaluateResult (e.g. `res.rows ?? res`).
  3. Align CLI and server versions.
  4. Verify auth/base URL so real errors aren't surfacing as malformed success payloads.

Example fix

// before
const rows = dispatchEvaluateResult(result);
if (!Array.isArray(rows)) throw new CommandExecutionError(...);
// after
const raw = dispatchEvaluateResult(result);
const rows = Array.isArray(raw) ? raw : (raw?.rows ?? []);
Defensive patterns

Strategy: type-guard

Type guard

function isSummaryRows(d) { return Array.isArray(d) || Array.isArray(d?.rows); }

Try / catch

try {
  const rows = await cli.run(['unread-summary']);
} catch (e) {
  if (String(e.message).includes('expected array of summary rows')) {
    console.error('unread-summary envelope changed — inspect raw payload / API version');
  } else throw e;
}

Prevention

When it happens

Trigger: The unread summary endpoint returns an object envelope ({rows:[...]} or {summary:{...}}), an error object that survived dispatchEvaluateResult, or a non-JSON success body instead of a top-level array.

Common situations: Server upgrade changing the response envelope; a 200-wrapped error from a gateway; querying against a different server (`--server`) whose API version differs.

Related errors


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

Appendix: source

Thrown at clis/slock/unread-summary.js:45

      if (sres.status === 401) return { kind: 'auth', detail: '/servers/ returned 401' };
      if (!sres.ok) return { kind: 'http', status: sres.status, where:'/servers/' };
      const servers = await sres.json();
      const ures = await fetch('${SLOCK_API_BASE}/servers/unread-summary', { credentials:'include', headers });
      if (ures.status === 401) return { kind: 'auth', detail: '/servers/unread-summary returned 401' };
      if (!ures.ok) return { kind: 'http', status: ures.status, where:'/servers/unread-summary' };
      const summary = await ures.json();
      const byId = {};
      (Array.isArray(servers) ? servers : []).forEach((s) => { if (s && s.id) byId[s.id] = s; });
      const rows = (Array.isArray(summary) ? summary : []).map((u) => {
        const s = byId[u.serverId] || {};
        return { serverId: u.serverId, slug: s.slug || '', name: s.name || '', unreadCount: u.unreadCount || 0 };
      });
      return { kind: 'ok', rows };
    `;
    const result = await page.evaluate(`(async () => { ${snippet} })()`);
    const rows = dispatchEvaluateResult(result);
    if (!Array.isArray(rows)) {
      throw new CommandExecutionError(`expected array of summary rows, got ${typeof rows} (contract drift?)`);
    }
    return rows;
  },
});

View on GitHub (pinned to 49907e53dc)