{"record":{"id":"301fde105afdcbcf","repo":"tinyhumansai/openhuman","slug":"discord-link-start-response-missing-required-strin","errorCode":null,"errorMessage":"Discord link start response missing required string field: linkToken","messagePattern":"Discord link start response missing required string field: linkToken","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/api/channelConnectionsApi.ts","lineNumber":79,"sourceCode":"  if (!Array.isArray(unwrapped)) {\n    throw new Error(`${context} returned an invalid response shape`);\n  }\n  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}","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/channelConnectionsApi.ts#L61-L97","documentation":"Thrown by expectDiscordLinkStart() in app/src/services/api/channelConnectionsApi.ts after the 'openhuman.channels_discord_link_start' RPC has already resolved successfully. The frontend peels any CLI envelope ({result, logs}) and then requires 'linkToken' to be a non-empty string. It signals a frontend/core response-contract mismatch: the Rust core answered the method but its payload carries no usable link token.","triggerScenarios":"Calling channelConnectionsApi.discordLinkStart() (Connections screen -> Discord managed-link flow) when the core returns {} or {instructions: '...'} without linkToken, a linkToken of a non-string type, an empty-string linkToken, or a CLI envelope whose inner result lacks the field.","commonSituations":"Frontend and Rust core versions out of sync (stale core pinned via OPENHUMAN_CORE_REUSE_EXISTING=1, or a half-updated desktop app); a core built without the channels feature returning a degraded handler shape; link-token minting failed core-side (persistence error) and the handler still returned an object.","solutions":["Restart the core (Settings -> Restart Core) or relaunch the desktop app so the frontend and core binaries match; unset OPENHUMAN_CORE_REUSE_EXISTING if it pins an old core","Inspect the raw response: POST the core /rpc endpoint with method openhuman.channels_discord_link_start and check whether result.linkToken exists and is non-empty","Fetch GET /schema on the core port and confirm channels_discord_link_start is registered (a gated-out domain yields unknown-method instead, which fails earlier)","Check core logs for link-token creation/persistence errors at request time","If developing the Rust handler, return exactly { linkToken: <non-empty string>, instructions: <string> } with camelCase keys"],"exampleFix":"// before\nconst { linkToken, instructions } = await channelConnectionsApi.discordLinkStart();\n\n// after\ntry {\n  const { linkToken, instructions } = await channelConnectionsApi.discordLinkStart();\n  showDiscordLinkInstructions(instructions, linkToken);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('linkToken')) {\n    surfaceNotice('Core/frontend version mismatch — restart the app, then retry the Discord link.');\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"type-guard","validationCode":"// Before launching the Discord flow, confirm the core registers the method:\nconst schema = await fetch(coreRpcBaseUrl + '/schema').then(r => r.json());\nif (!JSON.stringify(schema).includes('channels_discord_link_start')) {\n  hideDiscordManagedLink(); // version skew — avoid the guaranteed throw\n}","typeGuard":"function isDiscordLinkStart(v: unknown): v is { linkToken: string; instructions: string } {\n  const r = v as Record<string, unknown> | null | undefined;\n  const inner =\n    r && 'result' in r && 'logs' in r && Array.isArray(r.logs)\n      ? (r.result as Record<string, unknown>)\n      : r;\n  return (\n    !!inner &&\n    typeof inner.linkToken === 'string' &&\n    inner.linkToken.length > 0 &&\n    typeof inner.instructions === 'string'\n  );\n}","tryCatchPattern":"try {\n  const { linkToken, instructions } = await channelConnectionsApi.discordLinkStart();\n  showDiscordLinkInstructions(instructions, linkToken);\n} catch (e) {\n  if (e instanceof Error && /linkToken|instructions/.test(e.message)) {\n    surfaceNotice('Core/frontend version mismatch — restart the app and retry.');\n  } else {\n    throw e;\n  }\n}","preventionTips":["Update the desktop app and its embedded core together; never mix builds","Avoid OPENHUMAN_CORE_REUSE_EXISTING against an older core binary in daily use","Keep payload-shape contract tests (Vitest) asserting linkToken/instructions for the discord link RPC"],"tags":["rpc","validation","discord","channels","contract-mismatch"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}