Skip to content

fix(tasks,runners): prevent duplicate dispatch and runner finalization leaks#3998

Draft
cursor[bot] wants to merge 2 commits into
developfrom
cursor/critical-bug-investigation-511d
Draft

fix(tasks,runners): prevent duplicate dispatch and runner finalization leaks#3998
cursor[bot] wants to merge 2 commits into
developfrom
cursor/critical-bug-investigation-511d

Conversation

@cursor

@cursor cursor Bot commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Critical bug inspection found three correctness issues on develop from recent runner lifecycle work. This PR applies minimal fixes with regression tests.

1. Duplicate task dispatch when all runners are busy (critical)

Impact: A remote task could be dispatched twice (e.g. Ansible/Terraform run in parallel on the same task ID) when every runner is at capacity.

Trigger: ErrAllRunnersBusy requeue enqueued the task while it remained in the running set; a concurrent 5s queue tick could ClaimAndDequeue and spawn a second run() goroutine before EventTypeRequeued released running state.

Fix: Call onTaskStop before Enqueue, send EventTypeRequeued synchronously (same pattern as reconciler requeue paths).

2. Runner reports failed instead of stopped after emergency stop

Impact: Tasks force-stopped via terminated_jobs while Run() was unwinding were reported as failed instead of stopped.

Trigger: Server sends terminated_jobs → runner sets stoppedRun() returns error → error path overwrote terminal status.

Fix: Shared finalizeAfterRun helper respects IsFinished() before applying status.

3. HA reconciler leaks pool state when task already finished

Impact: In HA mode, when failTaskRunnerLost won the finalize lock but the DB already had a terminal status, running/active pool state leaked (capacity exhaustion, skipped autorun children).

Fix: Call finalizeRemoteTaskLocked on the finished early-return path; harden offline requeue with DB re-check and rollback on persist failure.

Validation

  • go test ./services/tasks/... ./services/runners/... — all pass
  • New tests: TestTaskRunner_ErrAllRunnersBusy_ReleasesRunningBeforeEnqueue, TestRunningJob_finalizeAfterRun_*, TestFailTaskRunnerLost_HA (finished-with/without End), TestRequeueTaskRunnerOffline_HA_SkipsWhenDBShowsRunning, persist-error rollback assertions
Open in Web View Automation 

cursoragent and others added 2 commits June 29, 2026 11:08
When every runner is at capacity, ErrAllRunnersBusy requeued tasks by
enqueueing them while they still sat in the running set, then sending
EventTypeRequeued from a defer. A periodic queue tick could
ClaimAndDequeue the task in that window and start a second dispatch on
the same TaskRunner.

Release running/active bookkeeping before enqueueing and notify the pool
synchronously, matching the reconciler requeue paths.

Co-authored-by: Denis Gukov <fiftin@outlook.com>
…finalization

Runner client: when applyTerminatedJobs emergency-stops a job and Run()
unwinds with an error, the error path overwrote stopped with failed.
Add finalizeAfterRun that respects an already-finished status.

HA reconciler: when failTaskRunnerLost wins the finalize lock but the DB
already has a terminal status, complete finalization instead of bailing
so pool/Redis state and autorun children are not leaked. Harden offline
requeue with a pre-mutation DB re-check and rollback on persist failure.

Co-authored-by: Denis Gukov <fiftin@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant