{"record":{"id":"a7cfb0b47b358004","repo":"ruvnet/ruflo","slug":"cannot-cancel-finished-tasks","errorCode":null,"errorMessage":"Cannot cancel finished tasks","messagePattern":"Cannot cancel finished tasks","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"v3/@claude-flow/swarm/src/domain/entities/task.ts","lineNumber":238,"sourceCode":"    this._error = error;\n    this._retryCount++;\n\n    if (this._retryCount >= this._maxRetries) {\n      this._status = 'failed';\n      this._completedAt = new Date();\n    } else {\n      // Reset for retry\n      this._status = 'queued';\n      this._assignedAgentId = undefined;\n    }\n  }\n\n  /**\n   * Cancel the task\n   */\n  cancel(): void {\n    if (this._status === 'completed' || this._status === 'failed') {\n      throw new Error('Cannot cancel finished tasks');\n    }\n    this._status = 'cancelled';\n    this._completedAt = new Date();\n  }\n\n  /**\n   * Check if all dependencies are satisfied\n   */\n  areDependenciesSatisfied(completedTaskIds: Set<string>): boolean {\n    for (const depId of this._dependencies) {\n      if (!completedTaskIds.has(depId)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/swarm/src/domain/entities/task.ts#L220-L256","documentation":"Task.cancel() refuses to cancel tasks whose status is 'completed' or 'failed' — terminal states cannot be undone by cancellation. Any other state (pending, queued, assigned, running, even cancelled again) is cancellable. This is the entity protecting result finality.","triggerScenarios":"Cancel requests arriving after the task already completed (user hits cancel on a finished job)\nTimeout supervisor cancelling every in-flight task, including ones that finished a tick earlier\nDouble-cancel of a 'failed' task by both a retry-abort path and a shutdown sweep\nUI showing stale status so operators cancel finished tasks","commonSituations":"At-least-once cancellation events without status checks\nShutdown routines sweeping the task store and cancelling everything regardless of state\nRace between a worker's completion and a deadline-triggered cancel","solutions":["Check task.status is not 'completed'/'failed' before cancel() (or make cancel idempotent: 'cancelled' re-cancel is already allowed by this guard, finished is not)\nBreak the completion/cancel race with per-task single-flight transitions\nDiscard stale cancel commands using task version numbers or completion timestamps\nIn UIs, disable cancel once the task reaches a terminal state"],"exampleFix":"// before\ncancelBtn.onclick = () => task.cancel(); // throws for finished tasks\n\n// after\ncancelBtn.onclick = () => {\n  if (task.status !== 'completed' && task.status !== 'failed') task.cancel();\n};","handlingStrategy":"type-guard","validationCode":"const cancellable = (t) => t.status !== 'completed' && t.status !== 'failed';\nif (cancellable(task)) task.cancel();","typeGuard":"function isCancellable(t) { return t.status !== 'completed' && t.status !== 'failed'; }\n// note: cancelling an already-'cancelled' task is legal (guard only blocks terminal states)","tryCatchPattern":"try { task.cancel(); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'Cannot cancel finished tasks') return; // already final\n  throw e;\n}","preventionTips":["Disable cancel affordances once a task reaches completed/failed","Sweep shutdown cancellers by status filter, not blanket cancel-all","Break cancel/complete races with single-flight transitions per task"],"tags":["task","state-machine","cancellation","task-lifecycle","typescript"],"backgroundTag":"state-machine-invalid-transition","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}