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
Same guard as task-list-server: after the in-page fetch resolves, task-list asserts the unwrapped result is an array of task rows. A non-array indicates the server response no longer matches the shape the snippet expects (`data.tasks` array), so the command throws a CommandExecutionError instead of mapping garbage rows.
Source
Thrown at clis/slock/task-list.js:60
await page.goto(SLOCK_HOME_URL);
const snippet = `
${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}
${channelResolveFragment(channel)}
const status = ${JSON.stringify(status)};
const qs = status ? ('?status=' + encodeURIComponent(status)) : '';
const tres = await fetch('${SLOCK_API_BASE}/tasks/channel/' + encodeURIComponent(channelId) + qs, { credentials:'include', headers });
if (!tres.ok) return { kind: tres.status===401?'auth':'http', status: tres.status, where: '/tasks/channel/:id' };
const data = await tres.json();
// Server contract: { tasks: [...] }. Reject anything else as drift.
if (!data || !Array.isArray(data.tasks)) {
return { kind: 'http', status: 200, where: '/tasks/channel/:id (expected {tasks:[]}, got drift)' };
}
return { kind: 'ok', rows: data.tasks };
`;
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((t) => ({
id: t.id ?? '',
taskNumber: t.taskNumber ?? null,
title: t.content ?? t.title ?? '',
taskStatus: t.taskStatus ?? t.status ?? '',
assigneeId: t.claimedById ?? t.assigneeId ?? null,
}));
},
});
View on GitHub (pinned to 49907e53dc)
Solutions
- Dump the raw page.evaluate result to see the real payload
- Update the snippet's row extraction path to match the current API (e.g. data.tasks → data.items)
- Verify auth/session validity; an error body with HTTP 200 commonly triggers this
- Align CLI and server versions, or add normalization before the Array.isArray assertion
Example fix
// before
return { kind: 'ok', rows: data.tasks };
// after
const list = Array.isArray(data?.tasks) ? data.tasks : (Array.isArray(data?.items) ? data.items : []);
return { kind: 'ok', rows: list }; Defensive patterns
Strategy: type-guard
Validate before calling
const payload = dispatchEvaluateResult(result);
if (!Array.isArray(payload) && payload?.tasks == null) {
console.error('Unexpected channel-tasks payload:', payload);
} Type guard
const isTaskRowArray = (v) => Array.isArray(v) && v.every((r) => r != null && typeof r === 'object');
Try / catch
try {
const rows = await listChannelTasks(channel, opts);
} catch (e) {
if (/expected array of rows/.test(e.message)) {
// inspect raw payload; verify auth and API schema
}
throw e;
} Prevention
- Keep the in-page snippet in sync with the server's response schema
- Verify auth/session when payloads suddenly change shape
- Add a contract test for the channel tasks endpoint
When it happens
Trigger: The channel-tasks endpoint returns an error envelope, object, or paginated wrapper instead of an array under data.tasks; snippet and dispatchEvaluateResult expectations diverge; auth rejected with a 200 error body.
Common situations: Slock API/schema upgrade renaming or wrapping `tasks`; proxy returning HTML; channel name resolving but response contract changed; stale cached CLI version against a newer server.
Related errors
- expected array of rows from server, got ${typeof rows} (cont
- Slock ${commandName} succeeded without returning task id ${e
- Slock task-status succeeded without returning task id ${expe
- expected array of summary rows, got ${typeof rows} (contract
- Bilibili ${label} API returned a malformed payload
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/495d25ae5dddbeb9.
Report an issue: GitHub.