{"record":{"id":"2b56f280134fb96d","repo":"tinyhumansai/openhuman","slug":"discord-link-complete-response-missing-required-bo","errorCode":null,"errorMessage":"Discord link complete response missing required boolean field: linked","messagePattern":"Discord link complete response missing required boolean field: linked","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/api/channelConnectionsApi.ts","lineNumber":90,"sourceCode":"  }\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');\n  const status = typeof record.status === 'string' ? record.status : '';\n  if (!status) {\n    throw new Error('Channel connect response missing status');\n  }\n  return {\n    status,\n    restart_required: Boolean(record.restart_required),\n    auth_action: typeof record.auth_action === 'string' ? record.auth_action : undefined,","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/channelConnectionsApi.ts#L72-L108","documentation":"Thrown by expectDiscordLinkComplete() while validating the 'openhuman.channels_discord_link_check' response: the payload object must contain 'linked' as a boolean (details is optional and tolerated as null/undefined). The polling loop in the Discord connect UI hits this when the core's link-check answer cannot be interpreted as a link state.","triggerScenarios":"discordLinkCheck(linkToken) resolves with a payload like {status: 'pending'}, {is_linked: true} (renamed field), {} (unknown/expired token handled by returning an empty object), or a CLI envelope whose result lacks a boolean 'linked'.","commonSituations":"Core/frontend version skew after a partial update; a Rust refactor renaming 'linked' or nesting it one level deeper; the link token expired and an older core returned an error-shaped object through a success path.","solutions":["Restart the core / relaunch the app to resync frontend and core builds","Curl openhuman.channels_discord_link_check with the pending linkToken and inspect the result object's shape","Check core logs for link-token lookup failures (expired/garbage token) that may be surfaced as a degenerate payload","If developing the handler, always include linked: bool in the response, even on lookup failure (linked: false + details.error)"],"exampleFix":"// before (core returns an error object through a success path)\n{\"error\": \"token not found\"}\n\n// after\n{\"linked\": false, \"details\": {\"error\": \"token not found\"}}","handlingStrategy":"type-guard","validationCode":"// Skip polling when the token is clearly invalid, so the check never runs against a dead token:\nif (!linkToken.trim()) stopPolling();","typeGuard":"function isDiscordLinkCheck(v: unknown): v is { linked: boolean; details?: Record<string, unknown> | null } {\n  const r = v as Record<string, unknown> | null | undefined;\n  const inner =\n    r && 'result' in r && 'logs' in r ? (r.result as Record<string, unknown>) : r;\n  return !!inner && typeof inner.linked === 'boolean';\n}","tryCatchPattern":"try {\n  const { linked } = await channelConnectionsApi.discordLinkCheck(token);\n  if (linked) finishLink();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('linked')) {\n    stopPollingAndWarn('Could not read link status — restart the app and relink.');\n  } else throw e;\n}","preventionTips":["Poll with a bounded number of attempts so a permanently bad shape cannot loop forever","Expire link tokens client-side (stop polling after N minutes) to avoid checking dead tokens","Pin the linked:boolean field in contract tests for channels_discord_link_check"],"tags":["rpc","validation","discord","channels","polling"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}