fix: don't send a re-queued task to backlog (#674)#677
Conversation
Changing a blocked task to "In Progress" writes status=queued. If the previous executor's poller was still alive, pollTmuxSession saw the queued status and returned Interrupted, which executeTask finalizes to backlog — immediately undoing the requeue. Distinguish a re-queue from a genuine interruption: pollTmuxSession now returns a new Requeued result for the StatusQueued case, and executeTask handles it by leaving the task queued (no backlog write) and waking the worker for a fresh run. The fresh run's KillAllWindowsByNameAllSessions tears down the stale window, so no duplicate session lingers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
QA: bug reproduced, and this PR fixes itReproduced #674 end-to-end by driving the real TUI + daemon against an isolated throwaway instance (the The precondition matters: the bug only fires when the executor's Repro on
|
| Blocked (before) | Bounced to Backlog (main bug) |
In Progress (this branch) |
|---|---|---|
![]() |
![]() |
![]() |
Note (duplicate PR)
There are two PRs fixing #674: this one and #676. They arrived via two independent intake paths — #676 was opened directly, and this branch came from TaskYou task 4949, which the Slack-feedback automation auto-created from the #674 report ~24 min later. Both add a Requeued flag so the parked poller preserves queued instead of finalizing to backlog; one nuance is this branch returns execResult{Requeued: true} while #676 returns {Interrupted: true, Requeued: true}. Both pass QA here (the finalizer checks Requeued first). Whichever lands, close the other.
QA harness: isolated ty instance, real executor spawn, VHS-rendered board shots. Not the live daemon/DB.
Adopts the cleaner signal from the parallel fix (#677): a re-queue is not a cancellation, so pollTmuxSession now returns {Requeued: true} rather than {Interrupted: true, Requeued: true}. Verified safe — the only reader of Interrupted is executeTask's finalizer else-if, which the Requeued branch already short-circuits (it kills the stale session and returns). Keeps #676's explicit Kill + early return, which close the brief window where the stale process would otherwise outlive the handoff. Test updated to assert the two signals are mutually exclusive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Closing in favor of #676 — both fix #674 the same way (a This wasn't wasted: #676 has adopted the cleaner signal from this branch — Both were QA-verified against the isolated harness; evidence is on #676. The duplicate came from two intake paths racing on the same issue — worth a dedup guard in the feedback→task automation so an issue that already has an open PR/task doesn't spawn another. |



Fixes #674.
Problem
Manually changing a blocked task to In Progress (Shift-S) can briefly show it In Progress and then bounce it back to Backlog.
showChangeStatusmaps "In Progress" toStatusQueued. If the previous executor's poller (pollTmuxSession) is still alive (e.g. the task was parked at a permission prompt), it sees the internalqueuedstatus and returnsInterrupted: true.executeTaskfinalizes every interrupted result toStatusBacklog— treating the deliberate requeue as a user cancellation and immediately undoing it.Fix
Distinguish a re-queue from a genuine interruption:
Requeuedfield toExecResult/execResult.pollTmuxSessionreturnsRequeued: true(notInterrupted: true) for theStatusQueuedcase.executeTaskhandlesRequeuedby leaving the task inqueued(no backlog write) and waking the worker viaTriggerProcessing().The task then resumes through a fresh executor run. The fresh run's
KillAllWindowsByNameAllSessionstears down the stale window, so no duplicate session lingers, and the deferredrunningTaskscleanup releases this run's slot. Genuine interruptions (context cancellation, moving to Backlog) still finalize to backlog as before.Tests
TestPollTmuxSessionRequeuesOnQueuedStatus: a queued task yieldsRequeued=true/Interrupted=false.internal/executorpackage passes;go vetandgolangci-lintclean.Minimal, targeted change — no unrelated files or behavior touched.
🤖 Generated with Claude Code