tinyhumansai/openhuman · error

Discord link start response missing required string field: i

Error message

Discord link start response missing required string field: instructions

What it means

Thrown by expectDiscordLinkStart() when the 'openhuman.channels_discord_link_start' response object's 'instructions' field is missing or not a string (linkToken already passed its check). The managed Discord link flow needs the instructions text to show the user what to paste into Discord, so the frontend refuses a payload without it.

Source

Thrown at app/src/services/api/channelConnectionsApi.ts:82

  return unwrapped as T[];
}

function expectObject<T extends object>(payload: unknown, context: string): T {
  const unwrapped = unwrapCliEnvelope<unknown>(payload);
  const record = asRecord(unwrapped);
  if (!record) {
    throw new Error(`${context} returned an invalid response shape`);
  }
  return record as T;
}

function expectDiscordLinkStart(payload: unknown): DiscordLinkStartResult {
  const record = expectObject<Record<string, unknown>>(payload, 'Discord link start');
  if (typeof record.linkToken !== 'string' || !record.linkToken) {
    throw new Error('Discord link start response missing required string field: linkToken');
  }
  if (typeof record.instructions !== 'string') {
    throw new Error('Discord link start response missing required string field: instructions');
  }
  return { linkToken: record.linkToken, instructions: record.instructions };
}

function expectDiscordLinkComplete(payload: unknown): DiscordLinkCheckResult {
  const record = expectObject<Record<string, unknown>>(payload, 'Discord link complete');
  if (typeof record.linked !== 'boolean') {
    throw new Error('Discord link complete response missing required boolean field: linked');
  }
  const details =
    record.details !== undefined && record.details !== null
      ? (record.details as Record<string, unknown>)
      : null;
  return { linked: record.linked, details };
}

function normalizeConnectResult(payload: unknown): ChannelConnectionResult {
  const record = expectObject<Record<string, unknown>>(payload, 'Channel connect');

View on GitHub (pinned to a221052e0d)

Solutions

  1. Restart the core / relaunch the app so core and frontend versions match (same fix as the linkToken variant of this error)
  2. Curl openhuman.channels_discord_link_start on /rpc and verify result.instructions is a string
  3. Check core logs: the instructions string is usually composed from the bot config; a missing bot handle/config yields no instructions
  4. If developing the handler, always set instructions (fall back to a static template string core-side rather than omitting the field)

Example fix

// Rust core: never omit the field
// before
if let Some(instructions) = build_instructions() { resp.insert("instructions", instructions); }

// after
resp.insert("instructions", build_instructions().unwrap_or_else(||
    "Send !start <token> to the OpenHuman bot in Discord.".to_string()));
Defensive patterns

Strategy: type-guard

Type guard

function hasInstructions(v: unknown): v is { instructions: string } {
  const r = v as Record<string, unknown> | null | undefined;
  const inner =
    r && 'result' in r && 'logs' in r ? (r.result as Record<string, unknown>) : r;
  return !!inner && typeof inner.instructions === 'string';
}

Try / catch

try {
  const r = await channelConnectionsApi.discordLinkStart();
  renderInstructions(r.instructions);
} catch (e) {
  if (e instanceof Error && e.message.includes('instructions')) {
    showFallbackInstructions(r_linkTokenIfAvailable); // static '!start <token>' copy
  } else throw e;
}

Prevention

When it happens

Trigger: discordLinkStart() resolves but the core returns {linkToken: '...'} with no instructions key, instructions: null / number / object, or a CLI envelope whose result carries only the token. Note: an empty string '' passes — only absence or a non-string type throws.

Common situations: Core version that mints the token but skips building instructions (e.g. bot-invite template missing or feature-flagged off); frontend/core skew after a partial update; a refactor of the Rust response struct dropping or renaming the field.

Related errors


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