{"record":{"id":"78e2aaa76a554f19","repo":"tinyhumansai/openhuman","slug":"finish-choosing-how-openhuman-runs-tap-continue-o","errorCode":null,"errorMessage":"Finish choosing how OpenHuman runs (tap Continue on the setup screen), then try signing in again.","messagePattern":"Finish choosing how OpenHuman runs \\(tap Continue on the setup screen\\), then try signing in again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/components/oauth/oauthAuthReadiness.ts","lineNumber":165,"sourceCode":"        'OpenHuman could not reach its local runtime. Quit and reopen the app, ' +\n        'then try signing in again.'\n      );\n    }\n    default:\n      return 'Sign-in is still starting up. Wait a few seconds and try again.';\n  }\n}\n\n/**\n * Lightweight preflight before opening the system browser for OAuth.\n * Blocks browser launch when the local auth runtime is not ready yet.\n * `waitForOAuthAuthReadiness()` starts the local core when needed.\n */\nexport async function prepareOAuthLoginLaunch(): Promise<void> {\n  const quick = await waitForOAuthAuthReadiness(8_000);\n  if (!quick.ready) {\n    warnLog(`${logPrefix} pre-launch readiness`, quick);\n    throw new Error(oauthAuthReadinessUserMessage(quick.reason));\n  }\n}\n","sourceCodeStart":147,"sourceCodeEnd":168,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/components/oauth/oauthAuthReadiness.ts#L147-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — tool call args (op=add)\n{ \"op\": \"add\", \"content\": \"Ship fix\", \"plan\": \"repro; fix; test\", \"allowedTools\": null }\n// -> Err: `plan` must be an array of strings\n\n// after\n{ \"op\": \"add\", \"content\": \"Ship fix\", \"plan\": [\"repro\", \"fix\", \"test\"] }\n// allowedTools omitted -> Ok(None), patch leaves tools unconstrained","handlingStrategy":"validation","validationCode":"// TypeScript — run before invoking the `todo` tool via RPC/relay\nconst STRING_ARRAY_KEYS = [\"plan\", \"allowedTools\", \"acceptanceCriteria\", \"evidence\"] as const;\n\nfunction normalizeTodoArgs(args: Record<string, unknown>): Record<string, unknown> {\n  for (const key of STRING_ARRAY_KEYS) {\n    const v = args[key];\n    if (v === undefined) continue;      // absent -> Ok(None), fine\n    if (v === null) { delete args[key]; continue; } // explicit null would fail as_array()\n    if (typeof v === \"string\") {        // delimited string -> split into array\n      args[key] = v.split(/[;\\n]/).map((s) => s.trim()).filter(Boolean);\n      continue;\n    }\n    if (!Array.isArray(v)) throw new Error(`\\`${key}\\` must be an array of strings`);\n  }\n  return args;\n}","typeGuard":"function isOptionalStringArray(v: unknown): v is string[] | undefined {\n  if (v === undefined) return true;\n  return Array.isArray(v) && v.every((item) => typeof item === \"string\");\n}\n\n// usage: if (!isOptionalStringArray(args.plan)) { /* coerce or reject before calling */ }","tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","json","agent-tools","todo","serde","schema-mismatch"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}