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
- Print the raw result and typeof to see the actual envelope.
- Unwrap the envelope in the snippet or dispatchEvaluateResult (e.g. `res.rows ?? res`).
- Align CLI and server versions.
- 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
- Keep server and CLI versions in sync
- Assert the rows array shape in tests against the live API
- Check for 200-wrapped gateway errors when shapes drift
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
- juejin recommend returned a malformed has_more flag
- Nowcoder detail returned a mismatched post entity type
- Slock ${commandName} succeeded without returning task id ${e
- expected array of rows from server, got ${typeof rows} (cont
- expected array of rows from server, got ${typeof rows} (cont
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/06a940e7f29e87a6.
Report an issue: GitHub.