tinyhumansai/openhuman · error
Finish choosing how OpenHuman runs (tap Continue on the setu
Error message
Finish choosing how OpenHuman runs (tap Continue on the setup screen), then try signing in again.
What it means
Thrown by the `todo` agent tool's argument parser (src/openhuman/agent/tools/todo.rs, optional_string_array) when an optional array-typed field is present in the tool-call JSON but its value is not a JSON array. The keys affected are `plan`, `allowedTools`, `acceptanceCriteria`, and `evidence`, all parsed inside patch_from_args for op=add/edit. The function short-circuits Ok(None) only when the key is absent (args.get(key) returns None), so any present-but-non-array value — a string, number, object, or an explicit JSON null — reaches value.as_array() and fails there. The error propagates out of Tool::execute via `?` at the patch_from_args call sites, so the tool call aborts before any board mutation happens.
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 the value a JSON array of strings: pass "plan": ["step 1", "step 2"] instead of "plan": "step 1; step 2" — check the tool's parameters_schema (todo.rs:78-102) which declares each of these fields as {type: array, items: {type: string}}.
- Omit the key entirely rather than passing null: optional_string_array returns Ok(None) when args.get(key) is None, but an explicit null fails the as_array() check. Delete the field from the payload to leave the patch slot unset.
- If you control the caller and cannot fix the payload shape, coerce before sending: split delimited strings on ';' / newline into arrays, and drop null-valued keys from the JSON object.
- As a library maintainer, make null benign by adding a null arm mirroring the approvalMode handling at todo.rs:253 — `if value.is_null() { return Ok(None); }` before the as_array() call — so models that emit explicit nulls do not fail the call.
Example fix
// before — tool call args (op=add)
{ "op": "add", "content": "Ship fix", "plan": "repro; fix; test", "allowedTools": null }
// -> Err: `plan` must be an array of strings
// after
{ "op": "add", "content": "Ship fix", "plan": ["repro", "fix", "test"] }
// allowedTools omitted -> Ok(None), patch leaves tools unconstrained Defensive patterns
Strategy: validation
Validate before calling
// TypeScript — run before invoking the `todo` tool via RPC/relay
const STRING_ARRAY_KEYS = ["plan", "allowedTools", "acceptanceCriteria", "evidence"] as const;
function normalizeTodoArgs(args: Record<string, unknown>): Record<string, unknown> {
for (const key of STRING_ARRAY_KEYS) {
const v = args[key];
if (v === undefined) continue; // absent -> Ok(None), fine
if (v === null) { delete args[key]; continue; } // explicit null would fail as_array()
if (typeof v === "string") { // delimited string -> split into array
args[key] = v.split(/[;\n]/).map((s) => s.trim()).filter(Boolean);
continue;
}
if (!Array.isArray(v)) throw new Error(`\`${key}\` must be an array of strings`);
}
return args;
} Type guard
function isOptionalStringArray(v: unknown): v is string[] | undefined {
if (v === undefined) return true;
return Array.isArray(v) && v.every((item) => typeof item === "string");
}
// usage: if (!isOptionalStringArray(args.plan)) { /* coerce or reject before calling */ } Prevention
- Treat 'no value' as an omitted key, never an explicit null — the parser only skips fields that are absent from the JSON object.
- When bridging free text (user input, LLM prose) into plan/acceptanceCriteria, split on delimiters and build the array yourself rather than passing the raw string.
- Validate payloads against the tool's parameters_schema (GET /schema or the Tool::parameters_schema output) before dispatch; it declares these fields as arrays of strings.
- If you maintain a caller that previously worked, re-check after schema changes — new optional array fields (evidence, allowedTools) added to add/edit hit the same validator.
When it happens
Trigger: Calling the `todo` tool with op=add or op=edit and one of: (1) `"plan": "step 1; step 2"` — a single string instead of an array; (2) `"allowedTools": null` — explicit null is Some(Value::Null) in serde_json, not a missing key, so as_array() returns None; (3) `"acceptanceCriteria": ["a"], "evidence": {"link": "..."}` — object where an array is expected; (4) a model serializing a comma-joined string or a map of step->text instead of a list. Each of plan/allowedTools/acceptanceCriteria/evidence at todo.rs:274-279 hits the same check at line 295.
Common situations: LLM tool-calling is the usual source: models frequently collapse `plan` into one delimited string, emit null for 'no value' instead of omitting the key, or nest objects (e.g. numbered steps as {"1": "..."}). Also hit when hand-writing RPC/JSON-RPC payloads to the core's tool surface, when a schema-drift between frontend expectations and the tool's declared JSON Schema (which says type: array, items: string for these four fields) goes unnoticed, or when an upstream orchestrator forwards user-typed free text verbatim into an array slot.
Related errors
- OpenHuman could not reach its remote (cloud) runtime. Check
- 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/78e2aaa76a554f19.
Report an issue: GitHub.