Leantime/leantime · error · Illuminate\Container\EntryNotFoundException
Can't find milestone
Error message
Can't find milestone
What it means
Tickets::getMilestoneProgress() loads the milestone with getTicket($milestoneId); when no matching ticket row exists it throws EntryNotFoundException ('Can't find milestone'). This exception type carries no JSON-RPC-specific code, so over the API it surfaces as a generic/untyped error rather than the -320xx family. Note that non-numeric string ids are passed through unconverted and effectively never match a row.
Source
Thrown at app/Domain/Tickets/Services/Tickets.php:1743
*
* @param int|string $milestoneId ID of the milestone.
* @return float The progress of the milestone as a percentage.
*
* @throws EntryNotFoundException If the milestone with the given ID is not found.
*
* @api
*/
#[RequiresPermission(TicketsPermissions::VIEW)]
public function getMilestoneProgress(int|string $milestoneId): float
{
if (is_numeric($milestoneId)) {
$milestoneId = (int) $milestoneId;
}
$milestone = $this->getTicket($milestoneId);
if (! $milestone) {
throw new EntryNotFoundException("Can't find milestone");
}
$prepareSearchParams = $this->prepareTicketSearchArray(['milestone' => $milestoneId, 'currentProject' => $milestone->projectId, 'currentSprint' => '']);
$tickets = $this->ticketRepository->getAllBySearchCriteria($prepareSearchParams);
$statusLabels = $this->getStatusLabels($milestone->projectId);
$defaultEffort = 3;
$defaultPriority = 3; // low number high priority high priority 1-5 low priority
// We want to take priority into consideration but not make it the main driver.
$priorityFactor = [
1 => 2,
2 => 1.75,
3 => 1.5,
4 => 1.25,
5 => 1,
];View on GitHub (pinned to 9a9f49f100)
Solutions
- Verify the milestone exists first — Tickets::getTicket((int) $milestoneId) — and skip/placeholder the progress display when it returns false
- Refresh the source list of milestones and re-request progress only for ids still present
- Catch EntryNotFoundException around the progress call and render '0% / n.a.' instead of failing the page
Example fix
// before
$percent = $ticketsService->getMilestoneProgress($milestoneId);
// after
if (! $ticketsService->getTicket((int) $milestoneId)) {
return 0.0; // or skip rendering this milestone
}
$percent = $ticketsService->getMilestoneProgress($milestoneId); Defensive patterns
Strategy: validation
Validate before calling
if (! is_numeric($milestoneId) || ! $ticketsService->getTicket((int) $milestoneId)) {
return 0.0; // or skip the progress widget
}
$progress = $ticketsService->getMilestoneProgress($milestoneId); Type guard
function isExistingMilestoneId(int|string $id): bool
{
return is_numeric($id) && $id > 0;
} Try / catch
try {
$percent = $ticketsService->getMilestoneProgress($milestoneId);
} catch (\Leantime\Core\Exceptions\EntryNotFoundException $e) {
// milestone deleted or foreign — render placeholder instead of failing the dashboard
$percent = null;
} Prevention
- Validate milestone ids (numeric, > 0, exists via getTicket) before requesting progress
- Re-fetch the milestone list when rendering dashboards rather than caching ids long-term
- Note this one throws EntryNotFoundException with no dedicated code — catch the class, not a code
When it happens
Trigger: Rendering milestone progress after the milestone (really a special ticket) was deleted; passing an id of 0 or a non-numeric string; a deep link or dashboard widget holding a stale milestone id from before a deletion or a data import.
Common situations: Stale dashboard/roadmap widgets after milestone deletion; import scripts with dangling milestone references; ids copied between environments where the row does not exist.
Related errors
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/228bc605b28b4bdc.
Report an issue: GitHub.