{"record":{"id":"37f59ef47944ebf9","repo":"Leantime/leantime","slug":"32002-37f59e","errorCode":"-32002","errorMessage":"The task you tried to edit could not be found.","messagePattern":"The task you tried to edit could not be found\\.","errorType":"exception","errorClass":"Leantime\\Core\\Exceptions\\NotFoundException","httpStatus":404,"severity":"error","filePath":"app/Domain/Tickets/Services/Tickets.php","lineNumber":2712,"sourceCode":"     * editor or above AND be assigned to the ticket's project (prevents\n     * cross-project IDOR via a smuggled ticket id).\n     *\n     * @param  int  $id  The ticket id to update\n     * @param  array  $values  The fields to update\n     * @return bool True on success (false only if the underlying write fails)\n     *\n     * @throws AuthorizationException If the caller is not an editor, or is not assigned to the ticket's project\n     * @throws NotFoundException If the ticket does not exist\n     *\n     * @api\n     */\n    #[RequiresPermission(TicketsPermissions::EDIT, entityScoped: true)]\n    public function patchTicket(int $id, array $values): bool\n    {\n        // getTicket() returns false when the user can't access the ticket's project.\n        $ticket = $this->getTicket($id);\n        if (! $ticket) {\n            throw new NotFoundException('The task you tried to edit could not be found.');\n        }\n\n        // Editor+ in the ticket's project (project-scoped role, not the session role) AND\n        // access to it. Replaces the prior session-scoped userIsAtLeast + assignment checks.\n        $this->authorize(TicketsPermissions::EDIT, (int) $ticket->projectId);\n\n        return $this->patch($id, $values);\n    }\n\n    /**\n     * Set a ticket's status from a semantic status type (\"new\" / \"inprogress\" / \"done\").\n     *\n     * Used by the program cross-project kanban: columns are status types, but the value\n     * written is always a real status key that exists in the ticket's OWN project, so a\n     * drag on the program board can never leave the task with a status its project board\n     * doesn't recognize. Authorization (edit in the ticket's project) is delegated to\n     * patchTicket().\n     *","sourceCodeStart":2694,"sourceCodeEnd":2730,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Tickets/Services/Tickets.php#L2694-L2730","documentation":"Tickets::patchTicket() fetches the ticket with getTicket(), which returns false BOTH when the id matches no row AND when the current user cannot access the ticket's project — so this NotFoundException (JSON-RPC -32002) deliberately doubles as the no-access answer, avoiding existence leaks. For tickets that do resolve, authorize(TicketsPermissions::EDIT, projectId) then enforces editor+ in the ticket's own project before the patch runs.","triggerScenarios":"Patching a deleted or wrong-pasted ticket id; dragging/editing a card from a stale board after the ticket was deleted mid-session; a ticket whose project the caller has no access to (indistinguishable from missing by design).","commonSituations":"Stale kanban/Gantt tabs during concurrent edits; scripts iterating ticket ids across projects the account cannot see; users removed from a project but still holding an open board.","solutions":["Confirm the id resolves for THIS caller first: Tickets::getTicket($id) must return a row (it applies the same access scoping)","Refresh the board and re-apply the edit with current ids","Catch NotFoundException (-32002) and treat it as 'not found or not yours' — prompt a reload rather than a retry"],"exampleFix":"// before\n$ticketsService->patchTicket($id, $values);\n\n// after\nif (! $ticketsService->getTicket($id)) {\n    return ['error' => 'Task missing or not accessible — reload the board'];\n}\n$ticketsService->patchTicket($id, $values);","handlingStrategy":"validation","validationCode":"$ticket = $ticketsService->getTicket($id); // false = missing OR out of scope\nif (! $ticket) {\n    return ['error' => 'Task not found or not accessible — reload'];\n}\n$ok = $ticketsService->patchTicket($id, $values);","typeGuard":null,"tryCatchPattern":"try {\n    $ok = $ticketsService->patchTicket($id, $values);\n} catch (\\Leantime\\Core\\Exceptions\\NotFoundException $e) {\n    // -32002 covers BOTH deleted and no-access by design — reload the board\n    $board->reloadTicket($id);\n}","preventionTips":["Run getTicket($id) first — it applies the same access scoping the patch uses","Read the -32002 message as 'not found OR forbidden' and always answer with a reload, not a retry","Keep board payloads fresh; drop cards that fail the pre-check instead of patching them"],"tags":["tickets","entity-not-found","authorization","kanban","json-rpc"],"backgroundTag":"entity-not-found","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}