{"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":"exception","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/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/services/api/channelConnectionsApi.ts#L85-L121","documentation":"Thrown by normalizeConnectResult() when a channel connect RPC response's `status` is missing or not a string (empty string after the typeof fallback fails the !status check). Status is the primary discriminator of a connect outcome (e.g. connected/pending_auth/restart_required); every other field (restart_required, auth_action, message) is optional and defaulted. Only a missing/non-string status is fatal.","triggerScenarios":"Core connect handler returned an error record or empty object through a success envelope; field renamed (state vs status) across versions; unwrapCliEnvelope stripped the payload layer containing status; provider threw mid-connect and a stub {ok:false} was returned.","commonSituations":"Connecting a messaging channel (Telegram/Slack/Discord/WhatsApp) on a version-skewed core; a provider-specific failure path that forgot to set status; test mocks omitting status.","solutions":["Restart/update the core so the channel connect controller returns {status: string, ...}.","Log the raw record on failure to check whether the field is renamed, nested, or the record is an error object that should have been thrown upstream.","Fix mocks/tests to include a status string.","If the core returns a nested payload (result.data), adjust expectObject/unwrap to target the right layer before validation."],"exampleFix":"// before\nconst status = typeof record.status === 'string' ? record.status : '';\nif (!status) {\n  throw new Error('Channel connect response missing status');\n}\n\n// after — surface what WAS returned to speed up triage\nconst status = typeof record.status === 'string' ? record.status\n  : typeof record.state === 'string' ? record.state : '';\nif (!status) {\n  throw new Error(`Channel connect response missing status (got keys: ${Object.keys(record).join(', ')})`);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isChannelConnectResult(v: unknown): v is { status: string; restart_required?: boolean; auth_action?: string; message?: string } {\n  return typeof (v as { status?: unknown } | null)?.status === 'string'\n    && ((v as { status: string }).status.length > 0);\n}","tryCatchPattern":"try {\n  const r = await connectChannel(provider);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Channel connect response missing status') {\n    notify('Could not read the connect result — restart Core and retry.');\n  } else throw e;\n}","preventionTips":["Include Object.keys(record) in validator failures for fast field-name triage.","Keep the core's connect controller and this validator in the same PR when changing the response shape.","Handle restart_required/auth_action after validating status, not before."],"tags":["channels","connect","schema-validation","core-rpc","status-field"],"backgroundTag":"schema-validation-failed","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}