Session scheduling freeze (Core API)
Operator handbrake for a single session. While frozen, the Core API stops all runner task scheduling for that session without deleting or mutating any task rows. Internal only, not exposed on v1/v2 public APIs.
Use it when a session is stuck in a task churn loop (for example a bad session template revision on site) and you need it to stop consuming runner capacity while you decide how to recover.
How it is stored
The flag is a freeze object in session metadata. No schema or session DTO change; the object rides inside the existing metadata blob:
{ "freeze": { "frozenAtUtc": "2026-07-02T00:45:00Z", "reason": "r020 template runaway" } }Managed by SessionFreezeMetadataHelper (merge, clear, read). Other metadata keys are preserved.
Endpoints
| Route | Effect |
|---|---|
POST /api/sessions/{id}/freeze?reason=... | Sets the freeze flag. reason is optional. 409 when already frozen. |
POST /api/sessions/{id}/unfreeze | Clears the flag. 409 when not frozen. |
POST /api/sessions/freeze-by-template/{sessionTemplateId}?reason=... | Mass freeze: sets the flag on every session using the template. Already-frozen sessions are skipped, not an error. 404 when the template does not exist. |
Freeze and unfreeze append a session timeline event (SessionFrozen = 30, SessionUnfrozen = 31); the mass variant appends SessionFrozen per session it freezes.
The mass endpoint returns a summary body (SessionFreezeByTemplateResult): total sessions on the template, frozen and already-frozen counts, and the session id lists for both. There is no mass unfreeze; unfreeze each session individually after verifying the cause is fixed.
What freeze gates
All checks live in RunnerTaskService and read the metadata flag app-side:
- Task creation:
CreateTaskAsyncrejects andShouldCreateTaskAsyncreturns false. Node status updates from still-running tasks (heartbeats, fail reports) can no longer mint child tasks or new ReferenceTime task rows. - Task claiming:
ClaimTaskAsyncfilters out tasks whose session is frozen. The existing backlog (Available and retryable Failed rows) stays in the database untouched but is invisible to runners. - Scheduler visibility:
GetPendingTasksAsync(feeds the task-types endpoint used for runner provisioning) andGetTaskPressureexclude frozen sessions, so a frozen backlog does not keep runner deployments provisioned or inflate pressure.
What freeze does not do
- It does not abort work already executing on a runner. In-flight executions run to completion; their signal, complete, and fail calls are still accepted so runners end cleanly. For an immediate stop, freeze first, then delete the runner pods; abandoned tasks are released on the runner’s next claim and are then unclaimable while frozen.
- It does not change retry semantics, supersede behavior, or task state for any other session.
- It does not block ingestion or data reads. Only runner task scheduling is gated.
Recovery flow for a runaway session
POST /api/sessions/{id}/freeze?reason=...- Optionally delete the runner pods currently executing the session’s tasks.
- Investigate or migrate the session (for example re-run under a corrected session template revision).
POST /api/sessions/{id}/unfreezeonly if the underlying cause is fixed; the untouched backlog becomes claimable again immediately.