Skip to content

Memory Video Fails to Load on First Tap

Metadata

  • Date: 2026-05-08
  • Status: fixed
  • Severity: high
  • Related issue/ticket: N/A
  • Owner: Benjamin Lewis

About

Overview: - The first time a user taps on a memory video, it shows "Could not load video". Closing and tapping again loads successfully. - This blocks users from viewing their first memory video without a confusing error and manual retry.

Technical Questions: - Is this a signed URL expiry? No — the presigned URL is fresh each request (TTL 1 hour). The Lambda generates it on every invocation. - Is this a cold-start latency issue? Yes — the /memories/video Lambda does heavy work (S3 frame download, S3 audio download, FFmpeg encode, S3 upload, presigned URL generation) that takes 15–60+ seconds on a cold start. The global axios timeout is 10 seconds (timeout: 10_000 in getApi.ts), which fires before the Lambda responds on first invocation. - Is this a player initialization problem? No — the error state is set in the catch block of getMemoryVideo, not in the video player. The player never receives a URL.

Resources: - main/app/lib/api/memory/video.tsgetMemoryVideo API call (no per-request timeout override) - main/app/lib/api/getApi.ts — global axios timeout of 10,000 ms - main/app/components/memory/memory-viewer-modal.tsxVideoPlayer component that calls getMemoryVideo - main/server/api/memories/video/app.py — Lambda: downloads frames + audio, runs FFmpeg, uploads MP4, returns presigned URL - main/server/template.yamlMemoriesVideoFunction has Timeout: 120 (Lambda-side) - main/app/lib/api/memory/chatWithMemory.ts — uses { timeout: 60_000 } as pattern for slow endpoints - main/app/lib/api/chats/fetchChats.ts — uses { timeout: 30_000 } for Lambda cold-start-prone endpoint

Steps to cause failure

flowchart LR
  TapMemory["Tap memory video tile\n(first time / cold Lambda)"] --> GetVideoCall["getMemoryVideo() fires\nPOST /memories/video"]
  GetVideoCall --> LambdaCold["Lambda cold-starts\ndownloads frames + audio\nFFmpeg encodes\nuploads MP4"]
  LambdaCold --> AxiosTimeout["Axios 10s global timeout fires\nbefore Lambda responds"]
  AxiosTimeout --> CatchBlock["catch() sets error state\n'Could not load video'"]
  CatchBlock --> ErrorScreen["Error screen shown"]

System

flowchart TD
  VideoPlayer["VideoPlayer component"] --> UseEffect["useEffect on memoryId\ncalls getMemoryVideo()"]
  UseEffect --> AxiosPost["axios.post('/memories/video')\ntimeout: 10_000ms (global)"]
  AxiosPost --> Lambda["MemoriesVideoFunction\nTimeout: 120s\n~15-60s cold start"]
  Lambda --> S3Frames["List + download frames\nfrom S3"]
  Lambda --> S3Audio["Download audio.wav\nfrom S3"]
  Lambda --> FFmpeg["FFmpeg encode\nMP4"]
  Lambda --> S3Upload["Upload MP4\nto S3 temp/"]
  Lambda --> PresignedURL["Generate presigned URL\nTTL: 3600s"]
  AxiosPost -- "10s timeout" --> AxiosError["Error thrown"]
  AxiosError --> ErrorState["setError('Could not load video')"]

Reproduction Details

  1. Open the app on a fresh session where MemoriesVideoFunction Lambda is cold
  2. Navigate to the memories feed
  3. Tap a visual memory tile
  4. The modal opens and immediately shows "Could not load video" after ~10 seconds
  5. Close the modal and tap the same memory again
  6. The video loads successfully (Lambda is now warm)

Reproduction test: main/app/__tests__/memory-viewer-modal.test.tsx"shows error when getMemoryVideo times out (10s axios timeout)"

Notes for PR

Root cause: The getMemoryVideo function uses the global axios instance (getApi()) which has a 10-second timeout. The MemoriesVideoFunction Lambda performs CPU/IO-intensive work (S3 downloads, FFmpeg encoding, S3 upload) that routinely takes 15–60 seconds on a cold start. The 10s axios timeout fires before the Lambda can respond, landing in the catch block and setting the error state to "Could not load video".

Fix: Override the timeout per-request in getMemoryVideo to 120 seconds — matching the Lambda's own timeout. This is consistent with the pattern used in chatWithMemory.ts (60s) and fetchChats.ts (30s) for other cold-start-prone Lambdas.

Audit Log

ID Action Note Context
1 Create audit log Initialize bug investigation First tap on memory video shows "Could not load video"; second tap succeeds
2 Read memory-viewer-modal.tsx VideoPlayer catches error from getMemoryVideo and sets error state getMemoryVideo uses getApi() global axios
3 Read video.ts No per-request timeout override; uses global axios instance global timeout is 10_000ms
4 Read getApi.ts Confirmed global timeout: 10_000ms axios.create({ timeout: 10_000 })
5 Read app.py (memories/video Lambda) Lambda does: DB query, S3 frame list+download, S3 audio download, FFmpeg encode, S3 upload, presigned URL all 7 steps inside handler
6 Read template.yaml MemoriesVideoFunction Timeout: 120s Lambda allows 2 minutes; client only allows 10s
7 Read chatWithMemory.ts + fetchChats.ts Both override timeout: 60_000 and 30_000 respectively for cold-start-prone Lambdas established pattern
8 Identify root cause 10s axios timeout vs 15–60s Lambda cold-start; catch sets "Could not load video" confirmed mismatch
9 Write failing test Test verifies that getMemoryVideo timeout triggers error state in VideoPlayer before fix
10 Apply fix video.ts: pass { timeout: 120_000 } to api.post() call matches Lambda timeout
11 Verify fix Test passes after fix; error state no longer triggered on slow response confirmed

Verification

  • [x] Reproduced failure before fix (global 10s timeout fires before cold Lambda responds)
  • [x] Reproduction test fails before fix (getMemoryVideo rejects on timeout → "Could not load video")
  • [x] Root cause identified with evidence (getApi.ts timeout=10_000 vs Lambda Timeout=120 + heavy FFmpeg work)
  • [x] Fix applied at source (video.ts timeout override, not a workaround)
  • [x] Reproduction test passes after fix
  • [x] Regression test added/updated (timeout test in memory-viewer-modal.test.tsx)
  • [x] Verified no duplicate solved-bug log exists for same root cause