{"record":{"id":"56344229f3a02618","repo":"tinyhumansai/openhuman","slug":"channel-connect-response-missing-status","errorCode":null,"errorMessage":"Channel connect response missing status","messagePattern":"Channel connect response missing status","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/services/api/channelConnectionsApi.ts","lineNumber":103,"sourceCode":"}\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,\n    message: typeof record.message === 'string' ? record.message : undefined,\n  };\n}\n\nfunction normalizePermissionCheck(payload: unknown): BotPermissionCheck {\n  const record = expectObject<Record<string, unknown>>(payload, 'Discord permission check');\n  const missing = Array.isArray(record.missing_permissions)\n    ? record.missing_permissions.filter((perm): perm is string => typeof perm === 'string')\n    : [];\n  return {\n    can_view_channel: Boolean(record.can_view_channel),\n    can_send_messages: Boolean(record.can_send_messages),\n    can_read_message_history: Boolean(record.can_read_message_history),","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/channelConnectionsApi.ts#L85-L121","documentation":"Thrown by normalizeConnectResult() when the 'openhuman.channels_connect' response object has no string 'status' field (status is read, coerced to '' when not a string, and the empty value throws). Connect results may carry optional restart_required/auth_action/message, but status is the one mandatory field the connect UI branches on.","triggerScenarios":"connectChannel(channel, payload) resolves with a payload whose status is missing, null, or non-string — e.g. the core returned {success: false, message: '...'} instead of a status-bearing result, or a CLI envelope whose inner result lacks status.","commonSituations":"Core/frontend contract drift after updating one side only; a channel whose connect handler short-circuits with a differently-shaped object (per-channel handlers differ); channel compiled out by a feature gate returning a stub shape.","solutions":["Restart the core / relaunch the app so both halves are the same build","Reproduce with curl: openhuman.channels_connect with the same channel/authMode/credentials and inspect the result object","Check the core logs for the specific channel's connect handler path — the shape is per-channel, so identify which channel produced it","If developing the handler, map every exit path to a status string ('connected', 'failed', 'restart_required', ...)"],"exampleFix":"// before\n{\"success\": false, \"message\": \"bad token\"}\n\n// after\n{\"status\": \"failed\", \"message\": \"bad token\", \"restart_required\": false}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isConnectResult(v: unknown): v is { status: string; restart_required?: boolean; auth_action?: string; message?: string } {\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.status === 'string' && inner.status.length > 0;\n}","tryCatchPattern":"try {\n  const res = await channelConnectionsApi.connectChannel(channel, payload);\n  handleStatus(res.status);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('missing status')) {\n    showConnectError('Unexpected core response — update/restart the app and retry.');\n  } else throw e;\n}","preventionTips":["Per-channel connect handlers diverge; contract-test the exact channel you touch","Map every core-side exit path to a status string before shipping a handler change","Resync app/core builds after partial updates before debugging deeper"],"tags":["rpc","validation","channels","connect","contract-mismatch"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}