tinyhumansai/openhuman · warning

Please paste a meeting link.

Error message

Please paste a meeting link.

What it means

Client-side validation in `joinMeetViaBackendBot`: `input.meetUrl.trim()` is empty. Thrown before any RPC, so the core/backend never saw the request. It guards the subsequent `openhuman.agent_meetings_join` call, which requires a join URL for the Recall.ai bot. Note the sibling `joinMeetingViaMascotBot` throws a plain object for the same condition — a caller catching Errors only would miss that one, but this function throws a real `Error`.

Source

Thrown at app/src/services/meetCallService.ts:228

  listenOnly?: boolean;
};

type CoreBackendMeetJoinResponse = { ok: boolean; meet_url: string; platform: string };

/**
 * Join a meeting via the backend's Recall.ai bot. Supports Google Meet,
 * Zoom, Microsoft Teams, and Webex.
 *
 * Calls the core RPC `openhuman.agent_meetings_join`, which emits `bot:join`
 * over the core's persistent Socket.IO connection to the backend. The backend
 * streams events back (`bot:reply`, `bot:harness`, `bot:transcript`, `bot:left`)
 * which the core bridges to the frontend as `agent_meetings:*` socket events.
 */
export async function joinMeetViaBackendBot(
  input: BackendMeetJoinInput
): Promise<{ meetUrl: string; platform: string }> {
  const meetUrl = input.meetUrl.trim();
  if (!meetUrl) throw new Error('Please paste a meeting link.');

  // Dual-mascot slots (issue #4277), mapped to the backend's snake_case wire
  // shape. Absent → backend falls back to `mascot_id`.
  const slots = input.mascots?.filter(m => m.mascotId?.trim());
  const mascots =
    slots && slots.length > 0
      ? slots.map(m => ({
          mascot_id: m.mascotId.trim(),
          name: m.name?.trim() || undefined,
          voice_id: m.voiceId?.trim() || undefined,
          rive_colors: mapRiveColors(m.riveColors),
        }))
      : undefined;

  // Flow/state metadata only — no participant names, voices, or the meet URL.
  log(
    'backend bot join corr=%s dual=%s slots=%d singleMascot=%s riveColors=%s',
    input.correlationId?.trim() || '-',

View on GitHub (pinned to a221052e0d)

Solutions

  1. Disable the Join button until `meetUrl.trim()` is non-empty
  2. Validate supported URL shape earlier (meet.google.com / zoom.us / teams.microsoft.com / webex) for a better message
  3. In catch handlers, show this message as a form-field hint, not a crash banner

Example fix

// before
<button onClick={() => joinMeetViaBackendBot({ meetUrl, ... })}>Join</button>

// after
<button disabled={!meetUrl.trim()}
        onClick={() => joinMeetViaBackendBot({ meetUrl, ... })}>Join</button>
Defensive patterns

Strategy: validation

Validate before calling

const url = meetUrl.trim();
if (!url) {
  setFieldError('Please paste a meeting link.');
  return;
}
await joinMeetViaBackendBot({ ...input, meetUrl: url });

Prevention

When it happens

Trigger: Join form submitted with an empty or whitespace-only URL field; UI wiring passes an uncontrolled input's initial value; a deep link / automation invokes the join flow without a URL parameter.

Common situations: Form submit handler not disabling the button until the field is non-empty; e2e tests driving the form with blank input; programmatic callers (flows) omitting meetUrl.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/f3e5508d86649a72. Report an issue: GitHub.