{"record":{"id":"426b89fb2d33806b","repo":"tinyhumansai/openhuman","slug":"discord-link-start-response-missing-required-strin-426b89","errorCode":null,"errorMessage":"Discord link start response missing required string field: instructions","messagePattern":"Discord link start response missing required string field: instructions","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/api/channelConnectionsApi.ts","lineNumber":82,"sourceCode":"  return unwrapped as T[];\n}\n\nfunction expectObject<T extends object>(payload: unknown, context: string): T {\n  const unwrapped = unwrapCliEnvelope<unknown>(payload);\n  const record = asRecord(unwrapped);\n  if (!record) {\n    throw new Error(`${context} returned an invalid response shape`);\n  }\n  return record as T;\n}\n\nfunction expectDiscordLinkStart(payload: unknown): DiscordLinkStartResult {\n  const record = expectObject<Record<string, unknown>>(payload, 'Discord link start');\n  if (typeof record.linkToken !== 'string' || !record.linkToken) {\n    throw new Error('Discord link start response missing required string field: linkToken');\n  }\n  if (typeof record.instructions !== 'string') {\n    throw new Error('Discord link start response missing required string field: instructions');\n  }\n  return { linkToken: record.linkToken, instructions: record.instructions };\n}\n\nfunction expectDiscordLinkComplete(payload: unknown): DiscordLinkCheckResult {\n  const record = expectObject<Record<string, unknown>>(payload, 'Discord link complete');\n  if (typeof record.linked !== 'boolean') {\n    throw new Error('Discord link complete response missing required boolean field: linked');\n  }\n  const details =\n    record.details !== undefined && record.details !== null\n      ? (record.details as Record<string, unknown>)\n      : null;\n  return { linked: record.linked, details };\n}\n\nfunction normalizeConnectResult(payload: unknown): ChannelConnectionResult {\n  const record = expectObject<Record<string, unknown>>(payload, 'Channel connect');","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/api/channelConnectionsApi.ts#L64-L100","documentation":"Thrown by expectDiscordLinkStart() when the link-start response object lacks a string `instructions` field — the human-readable steps shown to the user (typically 'DM this code to the bot'). linkToken may already be present; the validator requires both fields, so this fires specifically when the core returned a token but no instructions text. It is the second half of the response-shape contract for Discord pairing.","triggerScenarios":"Core handler version returns only {linkToken} (older/other implementation); instructions exists but is not a string (null, object); a serialization path drops the field when it contains formatting; envelope unwrap mis-targeting nested result objects.","commonSituations":"Cross-version pairing: UI from a newer release running against an older core binary; a fork/other host implementing the same RPC with a reduced field set; null returned by a core when the provider has no localized instructions.","solutions":["Update/restart the core so link.start returns both linkToken and instructions.","Log the raw record at the validator to confirm whether instructions is absent, null, or renamed (e.g. instruction / steps).","If the field is merely optional in your core version, default it in the validator with a generic instruction string instead of throwing.","Check the core's Discord connections controller code for the response struct to align field names."],"exampleFix":"// before\nif (typeof record.instructions !== 'string') {\n  throw new Error('Discord link start response missing required string field: instructions');\n}\nreturn { linkToken: record.linkToken, instructions: record.instructions };\n\n// after — graceful default keeps pairing usable\nconst instructions = typeof record.instructions === 'string' && record.instructions\n  ? record.instructions\n  : `Send this code to the bot in a DM: ${record.linkToken}`;\nreturn { linkToken: record.linkToken, instructions };","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasInstructions(v: unknown): v is { instructions: string } {\n  return typeof (v as { instructions?: unknown } | null)?.instructions === 'string';\n}","tryCatchPattern":"try {\n  const { instructions } = await startDiscordLink();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('missing required string field: instructions')) {\n    // core is older — fall back to a generic instruction around the token\n    showGenericInstructions();\n  } else throw e;\n}","preventionTips":["Default the instructions text from the token when the field is optional in your core version.","Align mock responses with the core's actual response struct.","Restart core after app updates to avoid schema drift."],"tags":["discord","channel-linking","schema-validation","core-rpc"],"backgroundTag":"schema-validation-failed","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}