jackwener/OpenCLI · error · CommandExecutionError

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

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.

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)

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.