π¨ Runbook: Job Stuck in processing
- Severity: π‘ High
- Time to mitigate: 5-10 min
- Time to root cause: 30-60 min
:::tip Automatic recovery now exists
Since the June 2026 reliability work, the hourly reconcileStuckJobs
scheduled function fails jobs stuck in pending/uploaded (> 2 h) or
processing (> 6 h) authoritatively β releasing the owner's concurrency slot
and refunding API-job cost. This runbook is for when you can't wait for the
sweep, when the sweep itself is failing, or when many jobs are stuck at once.
:::
Symptomβ
A user uploaded a video, the jobs/{jobId} document was created, it transitioned to status: "processing", but never reaches done. The Flutter app shows "Still processingβ¦" indefinitely (the client deliberately never times out processing β the server sweep owns failure).
Impactβ
- β Affected user can't see their analysis
- β GPU credit appears consumed but no result
- β Other users unaffected (usually)
Escalate to π΄ Critical if more than one user is affected simultaneously β this suggests a Lambda fleet outage or a deploy bug.
:::note Provider
Jobs run on the Lambda pull-worker fleet. RunPod was removed on 2026-08-16,
so log lines mentioning it are historical. A dead worker is requeued onto the
Lambda queue, and retryInfraFailedJobs re-dispatches infrastructure failures
automatically every 10 minutes.
:::
Diagnosisβ
Step 1 β Find the jobIdβ
From the user, or from the Admin panel β Jobs tab β filter by userId + most recent.
Step 2 β Check the Firestore docβ
# In Firebase Console β Firestore β jobs β {jobId}
# Or via CLI (requires firebase-tools)
firebase firestore:documents:get jobs/{jobId} --project acesense-prod
Note:
status(should berunning)videoPath(e.g.videos/<sessionId>/<filename>.mp4)resultPath(e.g.results/<sessionId>/<sessionId>_combined.json)updatedAtβ how long has it been stuck?
Step 3 β Did the video actually upload?β
gsutil ls gs://acesense-prod.firebasestorage.app/{videoPath}
If missing β the upload never completed. This is a client-side issue, not a pipeline issue. Jump to resolution option A.
If present β continue.
Step 4 β Did processVideoOnUpload fire?β
firebase functions:log --only processVideoOnUpload --project acesense-prod | grep {sessionId}
Look for:
πΉ Video uploaded: videos/{sessionId}/...β trigger fired βπ Sending to RunPod endpoint: ...β dispatched to RunPod ββ RunPod job created: <runpod_job_id>β RunPod accepted the job β
If the trigger never fired β Storage trigger is broken. Jump to resolution option B.
If RunPod error logged β Jump to RunPod errors runbook.
Step 5 β Did the result file get written?β
gsutil ls gs://acesense-prod.firebasestorage.app/{resultPath}
If missing β RunPod is still working on it or it crashed. Check RunPod console:
- runpod.io/console/serverless
- Select the AceSense endpoint
- Requests tab β find the job by timestamp
- Check its status + logs
If present β onResultUploaded trigger didn't fire. Jump to resolution option C.
Mitigation (make the pain stop)β
Immediate (unblock the user)β
-
Check if the result file exists (Step 5). If yes β manually mark the job done:
Admin Panel β Jobs β find job β click status badge β "Mark Complete"Or via a one-off console script (requires admin cred):
await admin.firestore().collection('jobs').doc('{jobId}').update({status: 'done',progress: 100,resultUrl: '<tokenized storage URL>',completedAt: admin.firestore.FieldValue.serverTimestamp(),updatedAt: admin.firestore.FieldValue.serverTimestamp(),}); -
If the result file doesn't exist and RunPod shows the worker crashed:
- Mark the job
status: "failed"with afailureReason - Tell the user to re-upload (there's no automatic retry today)
- Mark the job
If many users affectedβ
-
Rollback recent deploys β
firebase functions:logto identify the first failed job, check deploy history -
Disable uploads temporarily by deploying a rule that rejects new files to
videos/:match /videos/{path=**} { allow write: if false; }Revert once root cause is identified.
-
Post to Slack
#acesense-dev: "Pausing video uploads β investigating".
Resolution (fix the root cause)β
A. Upload never completedβ
- Check the user's client logs if available (Sentry / Crashlytics not wired yet β ask the user to send a screenshot)
- Common causes: network interruption, FFmpeg crash during chunking, insufficient device storage
- Fix: tell user to retry; long-term, add better client-side error reporting
B. Storage trigger not firingβ
firebase functions:log --only processVideoOnUploadshows noπΉ Video uploadedlines for this file- Check if the function is deployed:
firebase functions:list --project acesense-prod - Check deploy status: Firebase Console β Functions β find
processVideoOnUploadβ status should be "Active" - Check path filter β
filePath.startsWith("videos/")β is the path correct? - Fix: redeploy the function:
cd acesense-auth-function && firebase deploy --only functions:processVideoOnUpload
C. onResultUploaded didn't fireβ
- Result file exists in Storage but no Firestore update
- Common cause: file path doesn't match
results/prefix or doesn't end in_combined.json - Check the file path: is it
results/<sessionId>/<sessionId>_combined.jsonexactly? - Check the query:
jobs.where('resultPath', '==', <path>)β does the value in the job doc match the actual file path? - Fix: redeploy the function, or if a specific job is mis-pathed, update the
resultPathfield manually and re-upload the file to trigger the function again
Preventionβ
- Add a scheduled function that scans
jobsforstatus == "running"withupdatedAt > 30 min agoand marks themfailedwith a user-friendly message - Add a "retry" button to the Admin panel that re-enqueues a failed job without re-uploading
- Wire up Sentry / Crashlytics in the Flutter app so upload failures surface automatically
- Consider a periodic heartbeat write from RunPod to the job doc (
progressupdates) so stuck jobs are obvious
Postmortemβ
If this incident affected more than one user OR took more than 30 minutes to mitigate, write a postmortem. See the postmortem template (once it exists).
π― Relatedβ
- Debugging Quickstart β full decision tree
- Observability β where to look at what
- Job Document β job state and field contract
- Runbook: RunPod API Errors