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
- 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.
- 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")].
- Rebuild the Tauri app (pnpm run tauri:dev) so the registered command matches the frontend contract; stale dev builds are a frequent cause.
- 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
- Keep the TS SaveMeetingResponse interface in lockstep with the Rust response struct's serde attributes.
- Add an integration test asserting api_save_transcript returns a non-empty meeting_id.
- Never run a new frontend against a stale compiled Rust binary — rebuild both sides together.
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
- Failed to emit first-launch-detected event
- Whisper engine not initialized
- Parakeet engine not initialized
- App state not available
- Failed to access store: {}
AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16).
Data as JSON: /api/errors/83b1e626b7c38181.
Report an issue: GitHub.