tinyhumansai/openhuman · error
OpenHuman could not reach its remote (cloud) runtime. Check
Error message
OpenHuman could not reach its remote (cloud) runtime. Check your RPC URL and token in Settings, then try signing in again.
What it means
Thrown by the same optional_string_array helper in src/openhuman/agent/tools/todo.rs, but one step deeper: the field IS a JSON array (value.as_array() at line 294 succeeded), yet at least one element is not a string, so item.as_str() at line 299 returns None and the closure at line 300-302 builds this error during the collect() over the array. It means the container shape is right but the element type is wrong — numbers, booleans, nested arrays, or objects inside `plan` / `allowedTools` / `acceptanceCriteria` / `evidence` each trigger it, and the error names the field, not the offending index.
Source
Thrown at app/src/components/oauth/oauthAuthReadiness.ts:165
'OpenHuman could not reach its local runtime. Quit and reopen the app, ' +
'then try signing in again.'
);
}
default:
return 'Sign-in is still starting up. Wait a few seconds and try again.';
}
}
/**
* Lightweight preflight before opening the system browser for OAuth.
* Blocks browser launch when the local auth runtime is not ready yet.
* `waitForOAuthAuthReadiness()` starts the local core when needed.
*/
export async function prepareOAuthLoginLaunch(): Promise<void> {
const quick = await waitForOAuthAuthReadiness(8_000);
if (!quick.ready) {
warnLog(`${logPrefix} pre-launch readiness`, quick);
throw new Error(oauthAuthReadinessUserMessage(quick.reason));
}
}
View on GitHub (pinned to a221052e0d)
Solutions
- Make every element a JSON string: "plan": ["1. repro", "2. fix", "3. test"] instead of [1, 2, 3]; stringify numbers/booleans and flatten objects to their text field before sending.
- Flatten nested structures at the call site: [{"step": "repro"}] -> ["repro"], [["a", "b"]] -> ["a", "b"] — the tool accepts only flat string arrays for plan/allowedTools/acceptanceCriteria/evidence.
- Validate the payload against the tool's declared parameters_schema before invoking: every element of these four fields must satisfy typeof === 'string' (schema: items: {type: string}, todo.rs:78-102).
- As a library maintainer, if models routinely send objects, either stringify non-string elements (item.to_string() for numbers/bools) or include the offending index in the error message to shorten the model's retry loop.
Example fix
// before — tool call args (op=add)
{ "op": "add", "content": "Ship fix", "plan": [1, 2, 3] }
// -> Err: `plan` must be an array of strings
// after
{ "op": "add", "content": "Ship fix", "plan": ["repro", "apply fix", "run tests"] } Defensive patterns
Strategy: validation
Validate before calling
// TypeScript — element-level check before invoking the `todo` tool
function assertStringArrayField(args: Record<string, unknown>, key: string): void {
const v = args[key];
if (v === undefined || v === null) return;
if (!Array.isArray(v)) throw new Error(`\`${key}\` must be an array of strings`);
const badIndex = v.findIndex((item) => typeof item !== "string");
if (badIndex !== -1) {
throw new Error(`\`${key}\`[${badIndex}] is ${typeof v[badIndex]} — every element must be a string`);
}
}
for (const k of ["plan", "allowedTools", "acceptanceCriteria", "evidence"]) {
assertStringArrayField(todoArgs, k);
} Type guard
type StringArrayField = string[] | undefined;
function isFlatStringArray(v: unknown): v is string[] {
return Array.isArray(v) && v.every((item): item is string => typeof item === "string" && item.length > 0);
}
function flattenToStringArray(v: unknown): StringArrayField {
if (v == null) return undefined;
if (typeof v === "string") return v.split(/[;\n]/).map((s) => s.trim()).filter(Boolean);
if (Array.isArray(v)) return v.map((item) =>
typeof item === "string" ? item
: typeof item === "object" && item !== null ? JSON.stringify(item) // or pick .step/.text
: String(item));
return [String(v)];
} Prevention
- Never let structured elements (objects, nested arrays, numbers) reach plan/allowedTools/acceptanceCriteria/evidence — stringify or flatten them at the boundary.
- When an LLM produces the tool call, include an explicit example of the array shape in the prompt or rely on the tool's declared items: {type: string} schema rather than free-form examples.
- Note the error identifies only the field name, not the offending index — add your own index-reporting validation (as above) to avoid blind retry loops.
- One bad element rejects the whole add/edit call; validate every element, not just the first.
When it happens
Trigger: Calling `todo` with op=add/edit where an array field contains non-string elements: (1) `"plan": [1, 2, 3]` — a model numbering the steps instead of writing them out; (2) `"plan": [["repro", "fix"]]` or `"acceptanceCriteria": [{"text": "..."}]` — nested arrays/objects; (3) `"evidence": [true, 42]`; (4) `"allowedTools": ["shell", {"slug": "fs"}]`. Any single bad element fails the whole collect() at todo.rs:296-303, so the entire add/edit call is rejected even if other elements are valid strings.
Common situations: Typical LLM tool-call malformations: models emit numeric step indices, wrap each step as an object ({"step": "...", "done": false}), or mix a boolean into a checklist. Also occurs in programmatic callers that push enum values, numbers, or serde_json::Value::Object entries into what the tool schema (items: {type: string}) requires to be strings, and in version drift when a caller built against an older draft of the schema that allowed richer per-item structures.
Related errors
- Finish choosing how OpenHuman runs (tap Continue on the setu
- Failed to parse service CLI output as JSON: parsed value doe
- batch_id must be a kebab-case slug
- base_repo must be "tinyhumansai/openhuman" (got "${spec.base
- base_branch must be "main" (got "${spec.base_branch}")
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/dcce0987f10b627c.
Report an issue: GitHub.