{"record":{"id":"6e44aa29521303a0","repo":"Leantime/leantime","slug":"task-does-not-exist","errorCode":null,"errorMessage":"Task does not exist","messagePattern":"Task does not exist","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Domain/Tickets/Services/Tickets.php","lineNumber":3711,"sourceCode":"\n        // Collaborator relationship rows are cleaned up inside the repository's delticket().\n        if ($this->ticketRepository->delticket($id)) {\n\n            TicketDeleted::dispatch(ticketId: (int) $id, legacyHook: __FUNCTION__);\n\n            return true;\n        }\n\n        return false;\n    }\n\n    public function canDelete($id)\n    {\n\n        $ticket = $this->getTicket($id);\n\n        if (empty($ticket)) {\n            throw new \\Exception('Task does not exist');\n        }\n\n        $hasLoggedHours = $this->timesheetsRepo->getTimesheetsByTicket($id);\n\n        if ($hasLoggedHours) {\n            throw new \\Exception('Task has timesheets attached, delete all timesheets first or consider archiving the task');\n        }\n\n        return true;\n\n    }\n\n    /**\n     * @return bool|string[]\n     *\n     * @throws BindingResolutionException\n     *\n     * @api","sourceCodeStart":3693,"sourceCodeEnd":3729,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Tickets/Services/Tickets.php#L3693-L3729","documentation":"Tickets::canDelete($id) is the pre-delete guard used by the DelTicket controller (app/Domain/Tickets/Controllers/DelTicket.php:34) and, like every public service method, is callable over JSON-RPC as leantime.rpc.tickets.tickets.canDelete. It loads the ticket with getTicket($id); an empty result means no task with that id exists (already deleted, never existed, or wrong instance) and it throws Exception('Task does not exist').","triggerScenarios":"GET/POST /tickets/delTicket/{id} with an id that is already gone or invalid; JSON-RPC canDelete called with a stale id; double-submit where the first request deleted the task and the second hits the guard; scripts using hard-coded ids after a re-import wiped and re-created tasks.","commonSituations":"Two browser tabs (or a stale kanban board) both offering delete on the same task; frontend caches referencing removed tasks; API automation pointing at the wrong environment/DB; id=0 or string ids that never resolve.","solutions":["Verify the task exists first: call getTicket($id) (or GET /tickets/showTicket/{id}) before attempting the delete.","Refresh the board/task list in the UI before deleting to clear stale references.","Make API/scripts idempotent: treat 'Task does not exist' as success (the end state you wanted is already true).","Confirm you are talking to the right instance - check the id against zp_tickets.id in the DB you think you are using."],"exampleFix":"// before\ntry {\n    $this->ticketService->canDelete($id);\n    $this->ticketService->delete($id);\n} catch (\\Exception $e) {\n    // 'Task does not exist' indistinguishable from real failures\n}\n\n// after: check existence explicitly, treat a gone task as a no-op\nif (empty($this->ticketService->getTicket($id))) {\n    return response()->json(['status' => 'ok', 'detail' => 'already gone'], 200);\n}\n$this->ticketService->canDelete($id);\n$this->ticketService->delete($id);","handlingStrategy":"validation","validationCode":"if (empty($ticketService->getTicket($id))) {\n    // treat as already-deleted: idempotent no-op instead of an exception\n    return response()->json(['status' => 'ok', 'detail' => 'task already gone'], 200);\n}\n$ticketService->canDelete($id);","typeGuard":"/** True when a ticket with this id exists in the current instance. */\nfunction ticketExists(\\Leantime\\Domain\\Tickets\\Services\\Tickets $tickets, mixed $id): bool\n{\n    return ! empty($tickets->getTicket($id));\n}","tryCatchPattern":"try {\n    $ticketService->canDelete($id);\n} catch (\\Exception $e) {\n    if ($e->getMessage() === 'Task does not exist') {\n        return response()->json(['status' => 'ok', 'detail' => 'already deleted'], 200); // idempotent delete\n    }\n    throw $e; // timesheet guard and anything else stays an error\n}","preventionTips":["Check existence (getTicket) before every delete call.","Make delete flows idempotent - 'already gone' is success for a DELETE.","Refresh stale boards/lists before deleting from the UI.","In API scripts, resolve ids dynamically instead of caching them across re-imports."],"tags":["tickets","delete","not-found","api"],"backgroundTag":"entity-not-found","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}