# Change: Fix Interrupted Job Recovery

GitHub Issue: #317

## Why

When a Docker container (or application) is stopped while a scraping job is running, the job gets stuck in `RUNNING` status forever. On restart with `web` or `mcp` commands (`recoverJobs: false`), recovery logic is skipped, leaving the version stuck. The UI shows a spinning refresh button that cannot be clicked, and users must delete the library to recover.

Additionally, the current `recoverPendingJobs()` implementation (when `recoverJobs: true`) starts jobs from scratch instead of leveraging the existing refresh mechanism to continue where they left off.

## What Changes

- **When `recoverJobs: false`:** Mark all interrupted jobs (RUNNING/QUEUED status) as FAILED with message "Job interrupted" instead of leaving them stuck
- **When `recoverJobs: true`:** Use `enqueueRefreshJob()` for proper recovery that leverages existing pages and ETags, falling back to `enqueueJobWithStoredOptions()` for incomplete versions
- Add new `markInterruptedJobsAsFailed()` method to PipelineManager
- Refactor `recoverPendingJobs()` to use the refresh mechanism
- Handle edge case where recovery fails (no stored options) by marking job as FAILED

## Impact

- Affected specs: pipeline-recovery (new capability)
- Affected code:
  - `src/pipeline/PipelineManager.ts` - add `markInterruptedJobsAsFailed()`, refactor `recoverPendingJobs()`
  - `src/pipeline/PipelineManager.test.ts` - add tests for both recovery modes
  - `src/store/types.ts` - remove unused `isValidStatusTransition()` helper
