
mos-backend). MOS is a distributed content pipeline: a job fans out into shots, each shot into a chain of generative tasks (image_edit → frame_expand → video_generate → shot_reorder → upscale), which converge on an After Effects render on a Windows EC2 fleet, then delivery.running forever, a job pinned at processing, a customer's calendar entry that never becomes a video.productionIncident on the job and calendar entry, and route it to a human with the failure class already named — instead of letting it age silently as "overdue editor work."mos-scheduler, mos-worker-nexrender-healthcheck), a drain hook inside every SQS worker, a read-only cross-system audit API, and a set of dry-run-first repair CLIs._diag-* script, read CloudWatch, and hand-retried. Two base_clip_batch jobs sat stuck at 176/180 and 178/180 tasks with nothing in the system capable of finishing them.running forever Two-layer orphaned-task recovery: drain-reset on SIGTERM + scheduler reaper for hard kills. A worker death is recoverable, not permanent. SQS message dead-letters after maxReceiveCount — nothing will ever re-run it Reaper sends a brand-new message to the main queue (receive count reset to 1) without ever touching the DLQ. Retrying a live task would duplicate work TTLs are always larger than that queue's visibility timeout, so a message merely being redelivered is never reaped. Jobs stranded pending/waiting mid-chain leak a concurrency slot; enough of them drive availableSlots to 0 and the entire backlog stalls recoverStalledProcessingJobs fails them after a 3h no-activity TTL, before the concurrency count runs, so slots free up in the same tick. Render submitted, poll message lost — render task sits running while nexrender may already be done recoverStaleActiveRenders re-enqueues the poll first; only past the resubmit window does it ask nexrender directly, and it resubmits only when nexrender reports failure or no longer has the job. Retrying every failure forever burns credits on deterministic breakage An 8-class failure taxonomy splits retryable from operator_required; non-retryable classes are never auto-looped. Render server disk fills → silent black renders Health-check worker drives PowerShell over SSM: routine cleanup at <35 GB free, worker-stopping pressure cleanup at <15 GB. Render worker scheduled task dies on a Windows box Per-instance ASG sweep via SSM detects and restarts NexrenderWorker, publishing NexrenderWorkersRestarted. Each service healthy, but the systems disagree (credits vs. calendar vs. jobs vs. reviews vs. delivered media) Reliability control plane audits 6 named invariants across the whole customer workflow and reports contradictions. Failed production ages silently in the editor queue as overdue work Failures become a production_incident state, excluded from editor due/overdue metrics and surfaced in a separate Production Issues section.operator_required incidents, not an unbounded queue of stuck jobs.packages/workers/src/base.worker.tsreceiptHandle → taskId for in-flight messages. On SIGTERM/SIGINT (deploy, scale-in, Spot's 2-minute notice), stop() resets each in-flight task running → queued before releasing the message with visibility 0, so the next worker's running write wins the race. Only re-runnable generative steps are reset; render/render-poll/deliver have their own recovery. Entirely best-effort — a failure is logged and never blocks shutdown. Paired with ECS task scale-in protection, toggled on when a worker goes busy and off when it goes idle.scheduler.worker.tsreapStuckTasks() runs every 5-minute tick, in two passes:running pass — now - startedAt >= TTL(stepType).image_edit 12 min 5 min frame_expand 12 min 5 min video_generate 40 min 30 min shot_reorder 8 min 2 min upscale 15 min 5 minqueued pass — tasks that never got a startedAt and crossed their pickup TTL: the worker never claimed it.queued, bumps reapCount, and stamps a fresh startedAt so the TTL clock restarts. Re-running is safe because outputs are content-addressed (processedAssets cache + chainNextTask ordering) — a reap either cache-hits or regenerates. Bounded at 3 re-enqueues, 300 scanned and 50 acted on per tick; past the limit the task is failed and cascaded to the job via cascadeShotFailure / cascadeInputChainFailure and recorded through jobErrorService. It also normalizes legacy kebab-case step names, so old tasks can't hide from every TTL.dag-resolver.service.tsrecoverStrandedRenders — jobs flagged needsAeRerender where every shot is settled but the render task never got enqueued (lost the one-shot completion-callback race). Normalizes output-ready shots, completes their tasks, re-enqueues the render. Idempotent, no TTL.recoverStaleActiveRenders — render tasks running/pending with no poll activity for STALE_AE_RENDER_POLL_MS (3 min). Re-enqueues the poll message first. Elapsed time alone never kills or duplicates a render; a fresh submission happens only past the resubmit window and only when nexrender explicitly reports failure or has lost the job.recoverStalledProcessingJobs — the backstop for the gap nothing else covers. A job qualifies only when all hold: no task activity for STALLED_JOB_TTL_MS (3h), no running/queued task (that's the reaper's domain — never race it), and not needsAeRerender. It fails the job and its stranded tasks with a reason naming the exact shape (stranded pending/waiting vs. all tasks terminal but job never transitioned).production-failure-recovery.service.tsclassifyProductionFailure maps an error string onto 8 classes:infrastructure (ENOSPC, no space left on device) ✅ template_transfer (S3 copy, ECONNRESET, ETIMEDOUT) ✅ provider_timeout (5xx, 429, quota, 524, poll-count exceeded) ✅ stranded_pipeline (worker never claimed it, non-transitioning job) ✅ template_unavailable (bundle has no .aep) ❌ provider_rejection (safety settings, sensitive content, insufficient balance, HTTP 400) ❌ media_or_template (After Effects error, could not read from source) ❌ unknown ❌template_transfer failure against a template that is missing or unpublished is re-classified as template_unavailable — retrying it would never succeed.recoverRetryableFailedJobs then applies the guard rails before touching anything: the entry must be funded, not cancelled/delivered/approved, the review must exist and not be hidden, and the owner must still exist. Attempts are capped at 2 automatic recoveries with a 5-minute cooldown, and each attempt is reserved inside a Firestore transaction so two ticks can't double-retry the same job. Only the four transient classes may reset an exhausted retry budget.retryFailedProductionJob does the repair in one atomic batch: failed/dead tasks reset to pending — or waiting when the shot has unsatisfied dependencies — errors cleared, shot statuses restored dependency-aware, the job returned to pending, and the linked calendar entry and review re-pointed to queued. Whatever happens, a productionIncident is stamped on both job and entry with failureClass, attemptCount, detectedAt, lastAttemptAt and a status of retry_pending / retrying / exhausted / operator_required.nexrender-healthcheck.worker.tsInService ASG instances, check each in parallel over SSM PowerShell for NexrenderWorker task state, node process, and free disk.Windows\Temp\nexrender child directories (cleaning the parent misses them — it's continuously touched). Routine pass at 120-min staleness; under pressure, stop the worker, terminate aerender.exe/AfterFX.exe, and clean at 30-min staleness.picked/running — nexrender's DELETE removes the record without cancelling the job.NexrenderWorkersHealthy/Unhealthy/Restarted, NexrenderDiskFreeGbMinimum, NexrenderStuckJobs, ProductionHealthy, HealthCheckHeartbeat to CloudWatch.infraStatus/production-health with consecutiveFailures, statusChangedAt, lastHealthyAt — and fire a Slack alert only on status transition, including the recovery ("Production recovered: …").down health doc with a customer-legible message ("No After Effects workers are online. Jobs remain queued.").autoApproveDueTrialHeroes, materializeDueCalendarEntries, materializeDueLocalTours, sweepStuckDeferredCalendarGen(10 min) — for listings whose calendar generation was deferred on photo classification but whose post-batch trigger never fired — and the flag-gated sweepUnfundedEntriesToBlocked. Separately, GET /api/listings/:id/strategy self-heals: if the strategy doc is missing but the listing has a calendar, it returns regenerating: true and rebuilds in the background.reliability-audit.service.tsgetWorkspaceState, canonical billing usage, calendar, jobs, reviews, delivered media) and checks 6 invariants:SUBSCRIPTION_STATUS_DIVERGENCE, CREDIT_ALLOCATION_DIVERGENCE, UNFUNDED_WORK_IN_EDITOR_QUEUE, DELIVERED_WITHOUT_MEDIA, STALLED_PRODUCTION, ORPHANED_EDITOR_REVIEW, …) with critical/warning/info severity. Surfaced via GET /api/admin/reliability/incidents, GET /api/admin/reliability/users/:uid, scripts/audit-production-reliability.ts, and /internal/admin/reliability. All read-only by design — repairs are added one incident class at a time, only after false-positive rates are understood.GET /api/admin/health composes ALB, scaling, render-server, and alarm sub-checks plus /infra-status, and returns worst-of-all as the overall status. Each evaluate* is a pure reducer, so the rules are unit-tested without the AWS SDK. Tuning lives in one HEALTH_THRESHOLDS object: queue depth 100 → yellow / 1000 → red, heartbeat stale at 5 min, a service stuck desired != running for 10 min → red.api / workers / shared)CMD per servicemos-scheduler pinned at exactly 1 taskjobErrorEvents, infraStatus/production-health; transactions for recovery-attempt leases, batched writes for atomic repairMOS/Nexrender and MOS/Errors namespacesAsyncLocalStorage correlation-ID propagation; SQS publishers stamp MessageAttributes, workers re-bind in base.worker.tsAppError hierarchy (ExternalServiceError → VeoError / GeminiError / NexrenderError / …, plus PipelineStateError, ConflictError, CreditError), with classifyUnknown() as the single wrapping chokepointjobErrorService.record() as the single failure-recording chokepoint — classify → severity → jobErrorEvents → job counter → CloudWatch → deduped notification, wrapped so it can never break the callerview_infra_status, view_users)audit-production-reliability.ts (read-only), repair-editor-production-incidents.ts (--apply opt-in), reconcile-credit-lifecycle.ts, reconcile-editor-queue-lifecycle.ts, audit-editor-queue-health.tstask-reaper, production-failure-recovery, dag-resolver-stalled-jobs, nexrender-stale-recovery, nexrender-disk-recovery, reliability-audit.service, funding-sweep, health-aggregate-rolluptests/e2e-live) — UC13 covers strategy self-heal against a real deploymentpackages/workers/src/scheduler.worker.ts — the tick; every pass in its own try/catch, all before the production-pause gate, because recovery must run even while new dispatch is pausedpackages/workers/src/base.worker.ts — drain-reset, heartbeat, task protectionpackages/workers/src/utils/task-reaper.ts — TTL table + pure isTaskStale / step-name normalizationpackages/workers/src/nexrender-healthcheck.worker.ts + utils/nexrender-disk-recovery.tspackages/shared/src/services/dag-resolver.service.ts — the three render/job sweepspackages/shared/src/services/production-failure-recovery.service.ts — taxonomy + bounded retrypackages/api/src/services/reliability-audit.service.ts, health-aggregate.service.tsdocs/observability/orphan-task-recovery.md, docs/production-reliability-control-plane.md, docs/editor-production-incident-recovery.mdproductionIncident with status: operator_required and the exact class.hasRunningOrQueued → reaper's; needsAeRerender → stranded-render's; render/deliver → their own).availableSlots can never be starved to 0 by stalled work.production_incident, excluded from due/overdue metrics.
Posted Sep 20, 2026
Detects silently stalled pipeline work, orphaned tasks, lost renders, stalled jobs and recovers it with bounded, idempotent retries before escalating.
0
0
Blonde Waterfall