# Tasks: Unify Saved Tasks on SSH Services **Change:** `unify-tasks-on-services` **Phase:** tasks **Date:** 2026-06-19 ## Review workload forecast | Field | Value | |-------|-------| | Estimated changed lines | ~600–800 | | Chained PRs recommended | Yes (2 PRs) | | Chain strategy | stacked-to-main | ## Slice 1: Backend — shared runner + service-based tasks **Goal:** One execution path; tasks target ssh_tasks services; one history table. - [x] **1.1 Add shared `run_saved_task` helper** - Files: `backend/src/media_library_viewer_api/services/task_runner.py` (new) - Lines: ~90 - Details: `run_saved_task(store, task, service, *, request_id)` builds the SSH client from the service record (promote `_build_ssh_client`), renders the command, runs with the service timeout, appends a `service_task_runs` row, returns a `TaskRunResult`. - [x] **1.2 Rename saved_tasks column** - Files: `services/settings_store.py` (modify) - Lines: ~20 - Details: `default_machine_id` → `default_service_id` (ALTER TABLE RENAME COLUMN on startup; update `_row_to_task`, `_normalize_task_payload`, `upsert_task`). - [x] **1.3 Drop saved_task_runs** - Files: `services/settings_store.py` (modify) - Lines: ~-60 - Details: `DROP TABLE IF EXISTS saved_task_runs`; remove `record_task_run` and `list_task_runs` (task flavor). - [x] **1.4 Rewire tasks router** - Files: `routers/tasks.py` (modify) - Lines: ~70 - Details: `TaskInput.default_service_id`; `run_task` takes `service_id` (override), resolves an ssh_tasks service, calls `run_saved_task`; `/api/tasks/{id}/runs` reads `service_task_runs`. Remove `_resolve_machine_for_task` and `_client_for_machine`. - [x] **1.5 Widget delegates to shared helper** - Files: `widgets/sources.py` (modify) - Lines: ~-40 - Details: `SshTaskWidgetSource.fetch` calls `run_saved_task` instead of its inline run+log block. - [x] **1.6 Add `list_service_task_runs` by task (if not present)** - Files: `services/settings_store.py` (modify) - Lines: ~10 - Details: Confirm `list_service_task_runs(task_id=...)` covers the tasks router needs. - [x] **1.7 Update backend tests** - Files: `backend/tests/test_jobs.py`, `test_api.py` (modify) - Lines: ~60 - Details: Update task-run tests to the service model; cover override + default + disabled-service paths. - [x] **1.8 Verify** - Run: `cd backend && .venv/bin/ruff check . && PYTHONPATH=src .venv/bin/python -m pytest` **Slice 1 total:** ~250 changed lines. ## Slice 2: Frontend — Actions page on services **Goal:** Actions page targets ssh_tasks services; reads service_task_runs. - [x] **2.1 Update types** - Files: `frontend/src/types/index.ts` (modify) - Lines: ~15 - Details: `SavedTask` / `SavedTaskInput` `default_service_id`; `SavedTaskRun` aligned to `service_task_runs`. - [x] **2.2 Update API client** - Files: `frontend/src/api/client.ts` (modify) - Lines: ~10 - Details: `runTask(taskId, serviceId?)` sends `service_id`. - [x] **2.3 Rewire Actions page** - Files: `frontend/src/pages/Actions.tsx` (modify) - Lines: ~120 - Details: Task editor "Default service" select lists ssh_tasks services via `useServiceInstances("ssh_tasks")`; run dialog "Run on" selects an instance; run history reads `service_task_runs`. Remove `useMonitoringSettings`. - [x] **2.4 Update Actions tests** - Files: `frontend/src/pages/__tests__/Actions.test.tsx` (modify) - Lines: ~30 - Details: Mock `useServiceInstances`; update fixtures. - [x] **2.5 Docs + changelog** - Files: `docs/REQUIREMENTS.md`, `CHANGELOG.md` (modify) - Lines: ~30 - Details: Saved-actions section: tasks target ssh_tasks services; local mode dropped; breaking-upgrade note. - [x] **2.6 Verify** - Run: `cd frontend && npm run lint && npm run build && npm run test` **Slice 2 total:** ~200 changed lines. ## Integration and acceptance - [x] **3.1 Backend full test run** — `PYTHONPATH=src pytest`, all green. - [x] **3.2 Frontend full build/lint/test**. - [ ] **3.3 Manual dev-stack check**: - Create an ssh_tasks service; create a task with that default; run from Actions; see the run in both the Actions history and the service page. - Override the target at run time. - SSH task widget uses the same history.