Skip to content

feat(pipeline): failed-job quarantine + error classification + stage timings - #296

Merged
thcp merged 1 commit into
mainfrom
feat/failed-job-quarantine
Jul 17, 2026
Merged

feat(pipeline): failed-job quarantine + error classification + stage timings#296
thcp merged 1 commit into
mainfrom
feat/failed-job-quarantine

Conversation

@thcp

@thcp thcp commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #277, closes #294, closes #293. Phase 1 of #272 — plan: #272 (comment) (PR-1C + PR-1E, merged into one PR since they rewrite the same runner paths and the timings feed the quarantine's error.txt).

What

Failure quarantine (#277)

On pipeline failure the job dir no longer vanishes. Instead:

  1. error.txt is written: timestamp, job/title/source, stage, device, model, classified cause, stage timings, exception, and the demucs stderr tail (carried out of separate() by the new SeparationError).
  2. Heavy payloads (source, stem WAVs, video, demucs intermediates) are stripped — quarantines stay KB-scale.
  3. The dir moves to jobs/failed/<id>, expired after 7 days (STEMDECK_FAILED_TTL_SECONDS) by a new sweep_failed_jobs that runs unconditionally — persistent-library deployments (desktop, run.sh) skip the library TTL sweep but still expire failure evidence.
  4. Everything is best-effort with a fallback to plain removal, so a pathological error can never leak disk.

Guards verified with tests: the TTL sweep skips failed/ (it would otherwise delete the quarantine root as an "orphan dir" after 24 h), and registry.restore ignores it.

Error classification (#294)

New app/pipeline/errors.py: classify_failure() maps stderr/exception text → out-of-memory / unsupported-device / disk-full / bad-input / unknown. Surfaced as Job.error_detail (cause + last stderr line), rendered in the studio as a muted mono secondary line under the generic message. Phase 2's GPU→CPU fallback will reuse this classifier.

Stage timings (#293)

stage_timings ({download|prepare, analyze, separate, post}) recorded per job, persisted to metadata.json, included in error.txt, and emitted as a one-line completion summary:

[abc123] done device=cuda model=htdemucs_6s download=12.3s analyze=4.1s separate=88.0s post=9.2s total=113.6s

Job.compute_device records what the separation actually ran on.

Testing

  • New tests/test_errors.py (classifier table, SeparationError contract).
  • Runner: quarantine end-to-end (error.txt contents, payload stripping, original dir gone), timing summary + metadata persistence.
  • Sweep: failed/ root immune to the TTL sweep, quarantine TTL expiry, no-op without quarantine, restore ignores quarantine.
  • Updated the desktop sweep-loop test for the intended behavior change (TTL sweep skipped, failed sweep still runs).
  • Full suite: 171 passed; ruff check + ruff format --check clean; node --check on the JS.

…; stage timings

The error path destroyed all evidence: rmtree on failure threw away the
demucs stderr, the stage, and the device, leaving "Audio processing
failed" as the only artifact -- undebuggable after the fact.

- Failed jobs now move to jobs/failed/<id> with an error.txt recording
  stage, device, model, classified cause, stage timings, and the demucs
  stderr tail. Heavy payloads (source, stems, video) are stripped first
  so quarantines stay KB-scale. Expired after 7 days by a new sweep that
  runs even on persistent-library deployments (failure evidence is
  diagnostics, not library content). The TTL sweep skips failed/.

- New app/pipeline/errors.py: SeparationError carries the stderr tail +
  device out of separate(); classify_failure() maps failure text to
  out-of-memory / unsupported-device / disk-full / bad-input / unknown.
  The classified cause surfaces as Job.error_detail, shown in the studio
  as a muted secondary line under the generic error message.

- Per-stage wall-clock timings (download/prepare, analyze, separate,
  post) recorded on the job, written to metadata.json, included in
  error.txt, and emitted as a one-line completion summary with the
  compute device -- performance regressions and the CPU-vs-GPU question
  are now answerable from logs.

Closes #277
Closes #294
Closes #293
@thcp
thcp merged commit 378c64f into main Jul 17, 2026
15 of 20 checks passed
@thcp
thcp deleted the feat/failed-job-quarantine branch July 17, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generic error strings hide actionable causes No per-stage timing telemetry Error path destroys all evidence

1 participant