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

  1. 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.
  2. 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.
  3. 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).
  4. 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

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


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/dcce0987f10b627c. Report an issue: GitHub.