{"record":{"id":"02775698d0cf897e","repo":"jitsi/jitsi-meet","slug":"a-remote-participant-must-be-associated-with-a-jit","errorCode":null,"errorMessage":"A remote participant must be associated with a JitsiConference!","messagePattern":"A remote participant must be associated with a JitsiConference!","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"react/features/base/participants/actions.ts","lineNumber":235,"sourceCode":" *     type: PARTICIPANT_JOINED,\n *     participant: IParticipant\n * }}\n */\nexport function participantJoined(participant: IParticipant) {\n    // Only the local participant is not identified with an id-conference pair.\n    if (participant.local) {\n        return {\n            type: PARTICIPANT_JOINED,\n            participant\n        };\n    }\n\n    // In other words, a remote participant is identified with an id-conference\n    // pair.\n    const { conference } = participant;\n\n    if (!conference) {\n        throw Error(\n            'A remote participant must be associated with a JitsiConference!');\n    }\n\n    return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {\n        // A remote participant is only expected to join in a joined or joining\n        // conference. The following check is really necessary because a\n        // JitsiConference may have moved into leaving but may still manage to\n        // sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose\n        // (which is not outrageous given that leaving involves network\n        // requests.)\n        const stateFeaturesBaseConference\n            = getState()['features/base/conference'];\n\n        if (conference === stateFeaturesBaseConference.conference\n                || conference === stateFeaturesBaseConference.joining) {\n            return dispatch({\n                type: PARTICIPANT_JOINED,\n                participant","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/jitsi/jitsi-meet/blob/98de6219cc7ddbe07ace9fde045aff90a242ba01/react/features/base/participants/actions.ts#L217-L253","documentation":"participantJoined throws when it is called with a participant object that has no conference property, because remote participants are keyed by an id-conference pair in the participants reducer. This is a programmer/contract error inside the app (an invariant violation), not an environmental failure: the action creator requires every remote participant to be tied to a concrete JitsiConference instance.","triggerScenarios":"Calling participantJoined({ id, ... }) without a conference field; constructing a fake/virtual participant manually and forgetting to attach the JitsiConference; lib-jitsi-meet firing USER_JOINED with a null/undefined conference argument (e.g., virtual screenshare or whiteboard synthetic joins in older versions).","commonSituations":"New integration code (e.g., custom virtual participants like the handleSharingVideoStatus or focusWhiteboard callers listed) that passes a plain object without conference; upgrading lib-jitsi-meet where the USER_JOINED payload shape changed; tests/mocks that create participants without a conference stub.","solutions":["Pass the JitsiConference instance on the participant: participantJoined(dispatch, { id, conference, ... }) — audit the call site listed in the stack trace.","If the participant is genuinely local, use localParticipantJoined (or the local variant of the action) instead of participantJoined.","When consuming USER_JOINED events, guard: if (!conference) return; before building the participant payload.","If synthesizing virtual participants (screenshare/whiteboard), attach the active conference from redux state (getConference(state))."],"exampleFix":"// before\nparticipantJoined(fakeId, {\n    id: fakeId,\n    name: 'Virtual participant'\n});\n\n// after\nparticipantJoined(fakeId, {\n    id: fakeId,\n    name: 'Virtual participant',\n    conference: getConference(store.getState())\n});","handlingStrategy":"type-guard","validationCode":"const isRemoteParticipantPayload = (p: { conference?: JitsiConference }) => Boolean(p.conference);","typeGuard":"function isRemoteParticipant(p: { conference?: unknown }): p is { id: string; conference: JitsiConference } {\n    return typeof p.id === 'string' && p.conference instanceof Object && 'lock' in (p.conference as JitsiConference);\n}","tryCatchPattern":"try { dispatch(participantJoined(id, participant)); } catch (e) { logger.error('Malformed participant payload, missing conference', participant); }","preventionTips":["Always attach the active JitsiConference when building remote participant payloads.","Use localParticipantJoined for local users; participantJoined only for remotes.","Guard USER_JOINED handlers: skip events where conference is falsy.","In tests, stub a minimal conference object on all participant fixtures."],"tags":["participants","redux","invariant","lib-jitsi-meet"],"backgroundTag":"required-argument-missing","analyzedSha":"98de6219cc7ddbe07ace9fde045aff90a242ba01","analyzedAt":"2026-08-28T15:15:59.482Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}