Audio processing failure contract

July 23, 2026 · View on GitHub

This contract applies to macOS, iOS and its keyboard extension, Android, and Windows.

Top-down attempt lifecycle

Every platform follows the same six phases. Later optional work is never allowed to undo an earlier durable result.

PhaseDurable stateWhat must happen before moving onFailure result
0. Capture and finalizedurable preparation/capture journalAllocate the stable ID and managed partial source first, write every audio frame, monitor capture health, and close the container under a bounded stop/finalize deadlineReturn to idle with a microphone or storage message. Never submit a truncated or unfinalized source as a normal recording
1. Promote sourcecapture journal → processingValidate, fsync, and atomically promote the already-managed finalized partial, then commit its stable recording recordStop before recognition. Do not claim the recording is saved unless both the source and record are durable
2. Recognize speechprocessing or retryingRun one bounded local job or bounded sequential cloud requests; checkpoint completed leavesfailed, with source and the last durable ordered checkpoint
3. Clean up textactive state, with complete raw text already availableRun one separately bounded optional cleanup passRaw text becomes the result on timeout, HTTP error, invalid/empty/truncated output, or unavailable cleanup
4. Commit resultactive → success, failed, or cancelledPersist the terminal state before dismissing the workflowThe UI still becomes idle and explains that History could not be updated; startup recovery uses retained managed audio
5. Deliver textalready successPaste, insert, share, or hand off the durable textDelivery failure never returns the recording to processing; the text remains available to copy or retry delivery

The recording indicator belongs to phase 0. The visible processing indicator belongs only to phases 1–4. Clipboard access, app activation, usage reporting, analytics, and other delivery-side work must not hold it open.

Invariants

  1. Allocate the stable recording ID and managed partial source before capture. Persist and validate the finalized source before recognition.
  2. Every active attempt has one owner, a deadline, cancellation, and a terminal persisted state. Capture, finalization, recognition, and cleanup use distinct bounded stage deadlines; reaching the capture limit must still leave enough bounded time to close and validate the container.
  3. A relaunch converts abandoned active work to a recoverable failure; it never displays an endless processing state.
  4. A retry updates the same recording ID. It does not create duplicate history entries.
  5. Recognition is the required stage. Optional cleanup may improve successful text, but cleanup failure, timeout, or empty output returns the raw transcript.
  6. Chunk uploads run sequentially. Completed text is checkpointed in order, and cleanup runs once after the complete merge.
  7. Never delete the only recoverable audio copy. Never claim that a recording was saved until durable persistence succeeds.
  8. Every callback carries its recording ID, attempt ID, and deletion generation. Stale callbacks have no effect.

Persisted states

StateMeaningAllowed next states
processingSource is durable and one attempt owns the recordingsuccess, failed, cancelled
retryingA retry owns the same durable recordingsuccess, failed, cancelled
successFinal text is durable; raw audio remains available under retention policyretrying
failedWork ended; source and any completed partial text are durableretrying
cancelledThe user or a replacement attempt stopped the work; source remains recoverableretrying

If a platform does not expose cancelled separately in its UI, it may persist it as failed with a cancellation reason. It must still be terminal and retryable.

Request policy

FailureAttemptsRecovery
Complete 200 response1Accept only after the full body and strict response schema are available; 202 and 206 are not complete transcription results
Permanent 4xx (400, 401, 403, 404, 409, 422, and every other 4xx except 408, 413, 429)1Fail immediately; preserve source and partial text; show an actionable settings/request message
408, 429, 500...599Up to 3 totalRetry with bounded backoff; honor Retry-After and cap the delay at 10 seconds
Transient connection loss, DNS/connect failure, request timeoutUp to 3 totalSame bounded retry policy
I/O timeout or disconnect while draining a successful response bodyUp to 3 totalTreat it as a transient transport failure; retry the current request without replaying completed chunks
413No replay of completed chunksSplit only the rejected audio leaf, continue sequentially, and stop at a bounded depth/minimum size
Cancellation1Cancel the request/export/decoder immediately; do not retry or allow a late result to mutate state
Invalid response or decode error1Fail; preserve source and completed partial text
Local engine timeout/failure1 per attemptCancel/reset the engine, persist failure, allow an explicit retry
Cleanup timeout/error/empty outputCleanup is bounded separatelyKeep the successful raw transcript; do not convert recognition success into failure

Local and persistence failures

FailureState and recovery
Capture write, recorder health, or container finalization fails/stallsStop or detach the recorder under a bounded deadline, return the UI to idle, and do not start recognition from a source known to be truncated or unfinalized
Source move or initial journal write failsDo not start recognition. Return to idle with a storage message. Only say the recording was saved if the durable commit actually succeeded
Audio export, split, decode, or local model setup failsCancel/reset that operation, persist failed, retain the original source, and allow retry
A successful response body times out or disconnects before it is completeApply the bounded transient transport retry policy; never wait forever after headers
A fully received successful response has a malformed or undecodable bodyTreat it as an invalid response with one attempt. Preserve the source and checkpoint
Dictated text happens to look like JSONPreserve it literally for a text response. Only unwrap a JSON response when its media type and complete single-field schema identify a transcription envelope
A chunk returns empty textFail that leaf instead of silently omitting part of the recording. Preserve earlier ordered checkpoints
Complete single-file recognition contains no speechPersist a terminal no-speech failure; never leave a spinner active
Checkpoint write failsStop the attempt. Do not process later leaves that could no longer be recovered in order
Final terminal write failsEnd the in-memory processing state, retain the best source/text available, and show a truthful storage warning
Process or app dies while activeOn the next launch, normalize abandoned active rows to recoverable failed; expire overlays, live activities, keyboard handoffs, and in-memory sessions
Delete or Clear races active workA platform may block the action until active work stops. If deletion is accepted, it wins: late checkpoints must not recreate the row, and startup recovery must honor the deletion intent
Clipboard, accessibility, paste, or target-app delivery failsKeep success; the transcript stays in History and processing remains closed
Settings change during an attemptThe running attempt keeps its captured provider, model, language, vocabulary, and cleanup configuration; the next attempt uses the new settings

Bulk and chunk behavior

  • Split before upload when the known safe size is exceeded.
  • A server may enforce a smaller unknown limit; any rejected leaf can be split again.
  • Upload leaves one at a time so text order and resource use are deterministic.
  • After each successful leaf, persist the ordered merged checkpoint.
  • If a later leaf fails, the recording is failed with that checkpoint and the original source.
  • Retrying starts a new attempt for the same recording. Old attempt callbacks are ignored.

Relaunch recovery

At startup, normalize persisted processing and retrying entries to failed with a plain recovery message. Keep their audio and checkpoint text. Platform-specific in-memory spinners, overlays, live activities, and keyboard handoff sessions must also expire or restore to a terminal state.

Required deterministic scenarios

Each platform must cover capture-write failure, stalled finalization, permanent 4xx, retryable 408/429/5xx, a disconnected successful-response body, a fully received malformed response, repeated timeout, cancellation, initial and nested 413, stalled native chunk export, ordered three-chunk checkpointing, cleanup fallback, process death during processing and retry, two simultaneous retries competing for one recording ID, retry success without duplication, delete/clear racing a late callback, cancellation racing the first captured buffer, maximum-length capture still entering bounded finalization, storage failure, and concurrent jobs retaining their own provider configuration.