can1357/oh-my-pi · error
keys must be a string
Error message
keys must be a string
What it means
For the debug server's "keys" operation, #dispatch requires request.keys to be a string of key tokens (e.g. "ctrl+c", "a b", "'x'"). If keys is missing or of any other type (number, array, null), this error is thrown and returned to the client as a failed response. This is per-request payload validation before parseKeyTokens runs.
Source
Thrown at packages/tui/src/debug-server.ts:353
return { ok: true, tree: this.#tree() };
case "values":
return { ok: true, values: this.#values() };
case "info": {
const paint = this.#tui.getDebugPaint();
const focused = this.#tui.getFocused();
return {
ok: true,
columns: this.#tui.terminal.columns,
rows: this.#tui.terminal.rows,
pid: process.pid,
overlays: this.#tui.overlayStack.length,
focused: focused === null ? null : componentKind(focused),
alt_screen: paint?.altScreen ?? false,
cursor: paint?.cursor ?? { visible: false },
};
}
case "keys": {
if (typeof request.keys !== "string") throw new Error("keys must be a string");
const parsed = parseKeyTokens(request.keys);
for (const sequence of parsed.sequences) this.#tui.injectDebugInput(sequence);
return { ok: true, injected: parsed.events };
}
case "bytes":
if (typeof request.data !== "string") throw new Error("data must be a string");
this.#tui.injectDebugInput(request.data);
return { ok: true };
case "paste":
if (typeof request.text !== "string") throw new Error("text must be a string");
this.#tui.injectDebugInput(`\x1b[200~${request.text}\x1b[201~`);
return { ok: true };
case "mouse": {
if (typeof request.x !== "number" || typeof request.y !== "number")
throw new Error("mouse x and y must be numbers");
const action = request.action === undefined ? "click" : request.action;
if (typeof action !== "string") throw new Error("mouse action must be a string");
this.#tui.injectDebugInput(mouseSequence(request.x, request.y, action));View on GitHub (pinned to 9690622007)
Solutions
- Set keys to a single string of whitespace-separated key tokens, e.g. "ctrl+c a 'x'"
- Join a key array into a space-separated string before sending
- Check the request field name is exactly keys for the keys op
Example fix
// before
{ "op": "keys", "keys": ["ctrl", "c"] }
// after
{ "op": "keys", "keys": "ctrl+c" } Defensive patterns
Strategy: validation
Validate before calling
if (typeof request.keys !== "string")
throw new TypeError(`keys op requires a string, got ${typeof request.keys}`); Type guard
function hasStringKeys(r: { op: string; keys?: unknown }): r is { op: "keys"; keys: string } {
return r.op === "keys" && typeof r.keys === "string";
} Try / catch
try {
const res = await send({ op: "keys", keys });
} catch (err) {
if (String(err.message) === "keys must be a string") {
keys = String(keys);
}
} Prevention
- Join key arrays client-side: Array.isArray(keys) ? keys.join(" ") : keys
- Use the same request-schema type on both client and server
- Check the response's error field for per-request validation failures
When it happens
Trigger: Sending {"op":"keys"} without a keys field; sending keys as an array like ["ctrl","c"]; sending keys as a number or null.
Common situations: Client SDKs that JSON-encode a key list instead of joining it into a token string; omitting the field because the client assumed a default; copy-pasted request templates with a different op.
Related errors
- data must be a string
- text must be a string
- write() expects string, Blob, ArrayBuffer, or TypedArray dat
- string length intersection is unsatisfiable
- numeric range intersection is unsatisfiable
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/671ef504de041e9b.
Report an issue: GitHub.