can1357/oh-my-pi · error
data must be a string
Error message
data must be a string
What it means
For the debug server's "bytes" operation, #dispatch requires request.data to be a string, which is injected verbatim as a raw input sequence into the TUI. If data is missing or not a string, this error is thrown and returned as a failed response. Raw byte payloads must be provided as (escaped) text in JSON.
Source
Thrown at packages/tui/src/debug-server.ts:359
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));
return { ok: true };
}
case "quit":
return { ok: true };
default:
return { ok: false, error: `unknown op ${request.op}` };View on GitHub (pinned to 9690622007)
Solutions
- Set data to a JSON string containing the raw sequence with escapes, e.g. "\u001b[A" for arrow-up
- If you have a byte array, decode it to a UTF-8/latin1 string before sending
- Use the "paste" or "keys" ops instead when they match the intent
Example fix
// before
{ "op": "bytes", "data": [27, 91, 65] }
// after
{ "op": "bytes", "data": "\u001b[A" } Defensive patterns
Strategy: validation
Validate before calling
if (typeof request.data !== "string")
request.data = Buffer.from(request.data as number[]).toString("latin1"); Type guard
function hasStringData(r: { op: string; data?: unknown }): r is { op: "bytes"; data: string } {
return r.op === "bytes" && typeof r.data === "string";
} Try / catch
try {
const res = await send({ op: "bytes", data });
} catch (err) {
if (String(err.message) === "data must be a string") {
data = String.fromCharCode(...(data as number[]));
}
} Prevention
- Encode control characters as \uXXXX escapes in the JSON string
- Convert byte arrays to strings before sending; the wire format is JSON text
- Prefer the "keys" op for named keys and "paste" for text instead of raw bytes
When it happens
Trigger: Sending {"op":"bytes"} without data; sending data as an object like {"bytes":[27,91]}, a number, or a nested object.
Common situations: Clients that base64-encode or numeric-encode escape sequences and send the raw buffer instead of a JSON string; forgetting JSON-escaping for control characters like ESC.
Related errors
- keys 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/5f06fd7702e58c59.
Report an issue: GitHub.