Stop a snoozed task's container (and respawn it on wake) - #432
Merged
Merged
Conversation
Snoozing a task muted it on the dashboard while its container kept running, holding CPU, RAM and a model session for as long as the operator ignored it. `Spawner.pause` now stops the container of a task this runner claims that is actively snoozed, and releases the claim — which also clears the reported lifecycle phase, so the task composes `queued`. Waking needs no path of its own: a lapsed (or cleared) deadline leaves an unclaimed, un-snoozed task, exactly what `spawn_one` claims and spawns, with the per-task clone and the CLI session history in its config volume intact. `snoozed_until` stays a recorded fact the task service never compares to a clock; the arithmetic moves to `core/snooze.py` (`now` passed in) and is shared by the two callers that own a clock — the dashboard, which mutes the row, and the session service, which stops the container. Matching gates keep `spawn_one`, `reconcile` and `heal`/`mark_healing` off a paused task, so nothing in the same pass undoes the stop or reports the deliberate kill as a failure. Shell tasks are never paused: their script runs once, so stopping it would be a cancel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tildesrc
commented
Sep 23, 2026
| "e", | ||
| "snooze", | ||
| "Snooze", | ||
| "Snooze the highlighted task for 12 hours (stops its container)", |
Contributor
Author
There was a problem hiding this comment.
We don't need to specify this in the dashboard.
Contributor
Author
There was a problem hiding this comment.
Dropped — the e/E legend strings (and the matching rows in docs/dashboard.md) are back to their original wording. The action_snooze docstrings still describe the stop/respawn behavior; say the word if you'd rather those stayed out of the dashboard module too.
Review: the dashboard's help text doesn't need to spell out the container side-effect. Reverts the `e`/`E` legend strings (and the matching rows in the keybinding reference) to their original wording. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tildesrc
marked this pull request as ready for review
September 24, 2026 20:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Snoozing a task muted it on the dashboard while its container kept running, holding CPU, RAM and a model session for as long as the operator ignored it. Snooze now means "stop".
Spawner.pausestops the container of a task this runner claims that is actively snoozed, and releases the claim — which also clears the reported lifecycle phase, so the task composesqueued. Waking needs no path of its own: a lapsed (or cleared) deadline leaves an unclaimed, un-snoozed task, which is exactly whatspawn_oneclaims and spawns, through the full visible lifecycle, with the per-task clone and the CLI session history in its config volume intact — so the agent resumes where it left off.Stopping is unconditional, even mid-turn:
/workspaceis a host bind mount, so the checkout survives; what's lost is the in-container process, not work on disk.Task.snoozed_untilstays a recorded fact the task service never compares to a clock (the determinism invariant). The arithmetic moves tocore/snooze.py— pure,nowpassed in — and is shared by the two callers that own a clock: the dashboard (mutes the row) and the session service (stops the container), so the row you see muted and the container we stop are decided by the same rule.Matching gates keep the rest of the pass off a paused task, all against the same in-tick snapshot that still shows it claimed and live:
spawn_oneskips a snoozed task — so it neither comes up in the first place nor gets re-spawned right afterpausereleased it;reconcileskips it — otherwise it would readexit_reasonoff the container we just killed and reportfailed: container exited (exit 137);_is_orphan(sohealandmark_healing) skips it — claimed-with-no-session is the orphan shape, but a deliberate stop is not an orphan;spawnable_tasksmirrors the filter so the candidate list and the spawn can't disagree.Shell tasks are never paused: their script runs once, so stopping it would be a cancel, not a pause (the same reason
healskips them).No schema change (
snoozed_untilalready exists), no migration, and no REST/MCP surface change — pausing is entirely runner-side, over the existingreleaseendpoint.Plan: the task's
plan.mdartifact.Known gap, deliberately left: pressing
Ron a snoozed task releases its claim, and the snooze gate then holds it atqueued—Rdoes not force a wake. Auto-clearing the snooze onRis a separate call.🤖 Generated with Claude Code