Zackriya-Solutions/meetily · error · Error

No meeting ID received from save operation

Error message

No meeting ID received from save operation

What it means

After storageService.saveMeeting — which invokes the Tauri command api_save_transcript — the code reads responseData.meeting_id and throws when it is falsy. The TypeScript SaveMeetingResponse contract expects a snake_case meeting_id string, so this fires whenever the Rust command returns a body lacking that exact key: a serde rename_all = "camelCase" on the Rust response struct (yielding meetingId), a command returning a different shape, or a stale build registering an older api_save_transcript. The meeting may actually be saved in SQLite; only the returned ID is missing, which then breaks every follow-up step (language pinning, post-processing).

Source

Thrown at frontend/src/hooks/useRecordingStop.ts:265

        console.log('💾 Saving COMPLETE transcripts to database...', {
          transcript_count: freshTranscripts.length,
          meeting_name: savedMeetingName || meetingTitle,
          folder_path: folderPath,
          sample_text: freshTranscripts.length > 0 ? freshTranscripts[0].text.substring(0, 50) + '...' : 'none',
          last_transcript: freshTranscripts.length > 0 ? freshTranscripts[freshTranscripts.length - 1].text.substring(0, 30) + '...' : 'none',
        });

        try {
          const responseData = await storageService.saveMeeting(
            savedMeetingName || meetingTitle || 'New Meeting',  // PREFER savedMeetingName (backend source)
            freshTranscripts,
            folderPath
          );

          const meetingId = responseData.meeting_id;
          if (!meetingId) {
            console.error('No meeting_id in response:', responseData);
            throw new Error('No meeting ID received from save operation');
          }

          let shouldDetectSummaryLanguage = false;
          try {
            shouldDetectSummaryLanguage = !(await applyPinnedSummaryLanguageToMeeting(meetingId));
          } catch (error) {
            console.warn('Failed to apply pinned summary language preference for new meeting:', error);
            toast.warning('Could not apply default summary language', {
              description: 'The meeting was saved, but the default summary language was not applied.',
            });
          }

          if (shouldDetectSummaryLanguage) {
            try {
              await detectAndCacheSummaryLanguage(
                meetingId,
                freshTranscripts.map(t => t.text)
              );

View on GitHub (pinned to 0281737d87)

Solutions

  1. Inspect the console.error output of responseData and compare its keys with the TS SaveMeetingResponse type — a meetingId vs meeting_id casing mismatch is the most common cause.
  2. Check the Rust api_save_transcript return type: it must serialize snake_case meeting_id — remove #[serde(rename_all = "camelCase")] from the response struct or pin the field with #[serde(rename = "meeting_id")].
  3. Rebuild the Tauri app (pnpm run tauri:dev) so the registered command matches the frontend contract; stale dev builds are a frequent cause.
  4. Add a regression test that calls api_save_transcript and asserts response.meeting_id is a non-empty string.

Example fix

// before (Rust response struct)
#[derive(Serialize)]
#[serde(rename_all = "camelCase")] // meeting_id serializes as meetingId -> TS reads undefined
pub struct SaveTranscriptResponse { pub meeting_id: String }

// after — field serializes as meeting_id, matching SaveMeetingResponse
#[derive(Serialize)]
pub struct SaveTranscriptResponse { pub meeting_id: String }
Defensive patterns

Strategy: type-guard

Type guard

interface SaveMeetingResponse { meeting_id: string }
function hasMeetingId(r: unknown): r is SaveMeetingResponse {
  return typeof r === 'object' && r !== null
    && typeof (r as Record<string, unknown>).meeting_id === 'string'
    && (r as Record<string, unknown>).meeting_id !== '';
}

Try / catch

try {
  const responseData = await storageService.saveMeeting(title, transcripts, folderPath);
  if (!hasMeetingId(responseData)) {
    console.error('Malformed saveMeeting response:', responseData);
    // audio was already written to disk — run cleanup/recovery, don't abort silently
  }
} catch (e) {
  throw e;
}

Prevention

When it happens

Trigger: api_save_transcript resolves successfully but the response object has no meeting_id: Rust struct uses #[serde(rename_all = "camelCase"]), the command returns Ok(()) or a different struct, the field is Option and serialized as null, or frontend and Rust binaries are from different builds during development.

Common situations: Frontend/Rust contract drift during refactors; running pnpm dev against a stale compiled Tauri binary; a DB layer change that returns a success envelope without the generated row ID.

Related errors


AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16). Data as JSON: /api/errors/83b1e626b7c38181. Report an issue: GitHub.