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
After evaluating the in-page fetch snippet, task-list-server unwraps the result with dispatchEvaluateResult and asserts it is an array of task rows. If the browser-side script resolved to anything else (object, string, undefined), the command throws a CommandExecutionError flagging likely contract drift between the CLI's expected response shape and what the server returned.
Source
Thrown at clis/slock/task-list-server.js:52
throw new ArgumentError(`status "${status}" not in {${TASK_STATUSES.join('|')}}`);
}
await page.goto(SLOCK_HOME_URL);
const snippet = `
${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}
const status = ${JSON.stringify(status)};
const qs = status ? ('?status=' + encodeURIComponent(status)) : '';
const res = await fetch('${SLOCK_API_BASE}/tasks/server' + qs, { credentials:'include', headers });
if (!res.ok) return { kind: res.status===401?'auth':'http', status: res.status, where: '/tasks/server' };
const data = await res.json();
if (!data || !Array.isArray(data.tasks)) {
return { kind: 'http', status: 200, where: '/tasks/server (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 ?? '',
channelId: t.channelId ?? null,
assigneeId: t.claimedById ?? t.assigneeId ?? null,
}));
},
});
View on GitHub (pinned to 49907e53dc)
Solutions
- Log the raw `result` from page.evaluate to inspect the actual server payload
- Check the slock API version for schema changes and update the snippet's row extraction (data.tasks → new path)
- Confirm auth headers/session are valid — a 200 with an error body is the usual culprit
- Pin the working slock server version or add explicit shape normalization before the Array.isArray check
Example fix
// before
const rows = dispatchEvaluateResult(result);
if (!Array.isArray(rows)) throw new CommandExecutionError(...);
// after
const payload = dispatchEvaluateResult(result);
const rows = Array.isArray(payload) ? payload : (Array.isArray(payload?.tasks) ? payload.tasks : null);
if (!rows) throw new CommandExecutionError(`unexpected payload: ${JSON.stringify(payload).slice(0, 200)}`); Defensive patterns
Strategy: type-guard
Validate before calling
const payload = dispatchEvaluateResult(result);
if (payload != null && !Array.isArray(payload) && Array.isArray(payload.tasks)) {
// tolerate wrapped responses before the CLI's strict check
} Type guard
const isTaskRowArray = (v) => Array.isArray(v) && v.every((r) => r != null && typeof r === 'object');
Try / catch
try {
const rows = await listServerTasks(opts);
} catch (e) {
if (/expected array of rows/.test(e.message)) {
// log raw server payload and check API version/schema
}
throw e;
} Prevention
- Log raw API responses during slock server upgrades
- Pin compatible CLI/server versions
- Add a contract test asserting the tasks endpoint returns an array
When it happens
Trigger: The slock API returns a payload where `data.tasks` is missing/not an array (error envelope, HTML error page, paginated object, renamed field), or the snippet's `{ kind: 'ok', rows }` path changed so dispatchEvaluateResult yields a non-array.
Common situations: Slock server upgraded with a new response schema (e.g. tasks moved under data.items or wrapped in pagination); auth failure returning an error object with HTTP 200; a proxy/WAF intercepting the request; local snippet edited out of sync with dispatchEvaluateResult.
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/12f4b543125dc643.
Report an issue: GitHub.