fix(home): queued recordings show Transcribing instead of Couldn't transcribe, and Try Again no longer runs the summary model on a recording with no transcript

Merged
GI
33m ago
+1058-17

What users saw

Needs attention showed Couldn't transcribe for queued audio. Try Again ran the summary processor without a transcript, spun for a long time and returned to retry. First seal could arrive more than 24 hours after the wait began. Expired zero-content capture placeholders also appeared as failures.

What changes

  • Read-only Home/Activity presentation uses the later of the original wait and immutable first seal admission, plus the existing 24-hour maximum. Native seal and full-proof repair already stamp record_admitted_at once; no-job seal registration now does too. Reconnects, reseals, retries, requeues and reads do not renew it. A recording sealed 30h after capture shows Transcribing; a transcript arriving 2h later replaces that placeholder normally. No job lookup or write on reads.
  • Reprocess recognizes stamped batch captures without a transcript/receipt before any summary claim or model call. Stored structured.title is not transcript evidence. Inside the bounded wait it returns200 processing/transcribing; expired or explicit failures return409 transcription_pending. Build1563 at b78ea0112d82 and main decode200 and remove the failed row into processing;409 keeps the existing error toast. No owner-click priority scheme exists, so jobs, priority, worker claims and backoffs remain intact.
  • Expired in_progress batch-live placeholders with has_content=false and no committed audio span, transcript, active producers, partial/upload failure, summary debt/receipt or curation are read-only noise after the existing 2h pending deadline. Home lists hide them; Activity/detail uses completed/noise/No summary. All recordings and audio are preserved. Late content or a new pending generation releases the rule. This is not a stored STT-empty result.

Existing queued rows and bounded one-off

Already-stamped queued rows use the new bound immediately. A ro-prod metadata-only dry-run found four legacy retained-audio retry rows lacking seal/admission clocks. The lead can first run:

source ~/.local/bin/gcp-agent-env.sh ro-prod
/Volumes/Ephemeral/scratch/worktrees/omi/omi-lane-q12-ctx-default-upstream-keep-clean/backend/.venv/bin/python /Volumes/Ephemeral/scratch/worktrees/omi/omi-lane-q12-retry-pending-upstream-keep-clean/backend/scripts/repair_legacy_transcription_wait.py --dry-run --days 5 --limit 1000 --max-updates 4

The tool defaults to dry-run. A separately authorized operator apply writes only home_transcription_queue_observed_at once after rechecking source and ready/retry retained-audio jobs in a transaction. It never fabricates a seal time, changes original pending generations, jobs, custody, or admission metrics. The scan processes at most1000 owner rows and32 jobs/row, using one extra sentinel row/header at each limit to refuse overflow; transactional checks reread only state metadata. Writes are capped by --max-updates (at most20). Every repeated invocation retains the first observation. At most two display renewals per stored capture: first seal once and the legacy observation once. Five still-record_pending shapes are ineligible and await a real seal. No one-off was applied by this lane.

Kill switches and rollback

All new behavior ships ON by default with config/refactor_modes default_on parsing. Set an individual switch false to restore that behavior's prior decision:

  • HOME_TRANSCRIPTION_SEAL_WAIT_ENABLED: ignore first-seal and one-off observation clocks.
  • CONVERSATION_PENDING_TRANSCRIPTION_RETRY_ENABLED: restore the old summary reprocess path.
  • HOME_STALE_EMPTY_CAPTURE_NOISE_ENABLED: restore the old placeholder wire.

Existing queue-hold and Home-step switches retain their roles. No new default-off gate, GPU change, capacity change, provider switch, or provider-backlog release. No new model path or paid attempt; pending retries avoid summary calls. HOME_TRANSCRIPTION_MAX_AGE_SECONDS remains86400 and HOME_TRANSCRIPTION_DEADLINE_SECONDS remains7200. No serving-time or transcription-throughput promise.

INV-CAP-1: no audio/recording deletion, producer-fence relaxation or seal-proof change.
INV-DATA-1: account/data-plane identity stays pinned; the optional repair tool is owner-scoped and prints counts only.

Verification

On head aa4b73345c9db125d97a9542f1c377fb57fa5a83:

  • Full normal scripts/pre-push passed, including45 shared preflight checks, canonical backend typecheck (0 errors), bounded40-file unit phase and CPU guard, OpenAPI/generated-client compatibility, and pinned formatting. No skip flags, test cap or CPU threshold changed. The selector requests the full2901-file suite for the new database helper; full CI remains authoritative.
  • Lead premerge_guards.sh sweep passed17 files with0 failures. Cross-service deployment contract:33 passed,3 skipped.
  • All105 new focused tests pass through backend/test.sh:48 pending retry/read,39 stale empty,18 strict transactional one-off. Highest reported new call CPU0.18s, below the brief's0.4s caution point.
  • The final-head affected-import native run passed3842 tests across92 files, with4 emulator skips and86 expected xfails; per-file counts are in q12-an-retry-pending.md and the evidence JSON. Four imported integration files were invoked;46 cases skipped without loopback Firestore. No live contention claim.
  • Tests use one pytest process per file through omi-heavy, with GOOGLE_APPLICATION_CREDENTIALS and CLOUDSDK_* unset. Existing Homebrew libopus discovery uses DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib; no host installation/change.

Source-checked the released1563 and main decoder/provider contract; HTTP wire and no-model regressions pass. No app file changed.

UI-Evidence: none -- backend only

Not verified: physical phone/cache rendering, exact-head GitHub CI, production rollout/identity, one-off apply, real Firestore contention or transcription scheduling/speed/recovery. No push, PR, merge, deploy or production mutation by this lane. The fix does not drain the backlog.

Failure-Class: FC-queue-sla-presented-as-terminal-failure

Line-Count-Exception: backend/routers/conversations.py | 2702 -> 2713 | The reprocess owner checks pending capture evidence before claiming existing summary work; all policy stays in the small shared helper.
Line-Count-Exception: backend/utils/episodes/home_read.py | 573 -> 578 | Shared evidence fields and a presentation-only empty-placeholder guard stay at the existing projector boundary.

šŸ¤– Generated with Claude Code