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

  1. Verify the milestone exists first — Tickets::getTicket((int) $milestoneId) — and skip/placeholder the progress display when it returns false
  2. Refresh the source list of milestones and re-request progress only for ids still present
  3. 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

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.