Leantime/leantime · error · Leantime\Core\Exceptions\NotFoundException
-32002
-32002
Error message
A task referenced in the sort order could not be found.
What it means
Projects::sortProjects() is the authorized JSON-RPC entry point for Program Timeline (Gantt) re-sorting. Payload keys may be pgm-{id}, ticket-{id}, or bare numeric ids; ticket- keys are resolved to a real ticket via ticketRepository->getTicket(), and if that ticket does not exist it throws NotFoundException (JSON-RPC -32002). The check exists so a bogus task reference fails loudly instead of silently reordering part of the timeline.
Source
Thrown at app/Domain/Projects/Services/Projects.php:2774
* Validates manager+ access for every entity in the mixed payload (pgm-/ticket-
* prefixed and legacy numeric ids) before delegating to updateProjectSorting().
* Ticket ids are resolved to their project so the same manage-access rule applies.
*
* @param array $params Map of (pgm-{id}|ticket-{id}|{id}) => sort position
* @return bool True on success (false only if the underlying write fails)
*
* @throws NotFoundException If a ticket-{id} key references a ticket that does not exist
* @throws AuthorizationException If the caller cannot manage any item in the payload
*
* @api
*/
public function sortProjects(array $params): bool
{
foreach (array_keys($params) as $id) {
if (str_starts_with((string) $id, 'ticket-')) {
$ticket = $this->ticketRepository->getTicket((int) substr((string) $id, 7));
if (! $ticket) {
throw new NotFoundException('A task referenced in the sort order could not be found.');
}
$projectId = (int) $ticket->projectId;
} elseif (str_starts_with((string) $id, 'pgm-')) {
$projectId = (int) substr((string) $id, 4);
} else {
$projectId = (int) $id;
}
if (! $this->userCanManageProject($projectId)) {
throw new AuthorizationException('You are not allowed to re-sort one or more of these items.');
}
}
return $this->updateProjectSorting($params);
}
/**
* Authorized JSON-RPC entry point for patching a single project (e.g. GanttView on GitHub (pinned to 9a9f49f100)
Solutions
- Refresh the Program Timeline so the payload only contains current task ids, then re-apply the sort
- Filter the payload first: for each ticket-{id} key, confirm Tickets::getTicket((int) substr($id, 7)) returns a row before including it
- Catch NotFoundException (-32002) and surface a 'task no longer exists — reload' message instead of a generic failure
Example fix
// before
$projectsService->sortProjects($params); // throws on stale ticket-42
// after
$params = array_filter($params, function ($pos, $id) use ($ticketsService) {
if (str_starts_with((string) $id, 'ticket-')) {
return (bool) $ticketsService->getTicket((int) substr((string) $id, 7));
}
return true;
}, ARRAY_FILTER_USE_BOTH);
$projectsService->sortProjects($params); Defensive patterns
Strategy: validation
Validate before calling
foreach (array_keys($params) as $id) {
if (str_starts_with((string) $id, 'ticket-')) {
if (! $ticketsService->getTicket((int) substr((string) $id, 7))) {
unset($params[$id]); // drop stale task references pre-call
}
}
} Try / catch
try {
$ok = $projectsService->sortProjects($params);
} catch (\Leantime\Core\Exceptions\NotFoundException $e) {
// -32002: a ticket-{id} key is gone — reload the timeline and re-apply
$board->reload();
} Prevention
- Send sort payloads from freshly rendered boards; avoid replaying captured payloads
- Validate every ticket-{id} key with Tickets::getTicket() before including it
- On -32002, prompt a reload instead of retrying the same payload
When it happens
Trigger: Sorting the Gantt after another user deleted a task that is still rendered in your stale view; a hand-built or replayed payload containing a wrong/truncated ticket id (the digits after 'ticket-' are cast to int); a ticket id from a different Leantime instance.
Common situations: Long-lived program-timeline tabs; concurrent editing where tasks are deleted mid-session; automation scripts replaying captured sort payloads after data changes.
Related errors
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/e7b1c035d4d8df30.
Report an issue: GitHub.