{"record":{"id":"228bc605b28b4bdc","repo":"Leantime/leantime","slug":"can-t-find-milestone","errorCode":null,"errorMessage":"Can't find milestone","messagePattern":"Can't find milestone","errorType":"exception","errorClass":"Illuminate\\Container\\EntryNotFoundException","httpStatus":null,"severity":"error","filePath":"app/Domain/Tickets/Services/Tickets.php","lineNumber":1743,"sourceCode":"     *\n     * @param  int|string  $milestoneId  ID of the milestone.\n     * @return float The progress of the milestone as a percentage.\n     *\n     * @throws EntryNotFoundException If the milestone with the given ID is not found.\n     *\n     * @api\n     */\n    #[RequiresPermission(TicketsPermissions::VIEW)]\n    public function getMilestoneProgress(int|string $milestoneId): float\n    {\n\n        if (is_numeric($milestoneId)) {\n            $milestoneId = (int) $milestoneId;\n        }\n\n        $milestone = $this->getTicket($milestoneId);\n        if (! $milestone) {\n            throw new EntryNotFoundException(\"Can't find milestone\");\n        }\n\n        $prepareSearchParams = $this->prepareTicketSearchArray(['milestone' => $milestoneId, 'currentProject' => $milestone->projectId, 'currentSprint' => '']);\n        $tickets = $this->ticketRepository->getAllBySearchCriteria($prepareSearchParams);\n\n        $statusLabels = $this->getStatusLabels($milestone->projectId);\n\n        $defaultEffort = 3;\n        $defaultPriority = 3; // low number high priority high priority 1-5 low priority\n\n        // We want to take priority into consideration but not make it the main driver.\n        $priorityFactor = [\n            1 => 2,\n            2 => 1.75,\n            3 => 1.5,\n            4 => 1.25,\n            5 => 1,\n        ];","sourceCodeStart":1725,"sourceCodeEnd":1761,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Tickets/Services/Tickets.php#L1725-L1761","documentation":"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.","triggerScenarios":"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.","commonSituations":"Stale dashboard/roadmap widgets after milestone deletion; import scripts with dangling milestone references; ids copied between environments where the row does not exist.","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"],"exampleFix":"// before\n$percent = $ticketsService->getMilestoneProgress($milestoneId);\n\n// after\nif (! $ticketsService->getTicket((int) $milestoneId)) {\n    return 0.0; // or skip rendering this milestone\n}\n$percent = $ticketsService->getMilestoneProgress($milestoneId);","handlingStrategy":"validation","validationCode":"if (! is_numeric($milestoneId) || ! $ticketsService->getTicket((int) $milestoneId)) {\n    return 0.0; // or skip the progress widget\n}\n$progress = $ticketsService->getMilestoneProgress($milestoneId);","typeGuard":"function isExistingMilestoneId(int|string $id): bool\n{\n    return is_numeric($id) && $id > 0;\n}","tryCatchPattern":"try {\n    $percent = $ticketsService->getMilestoneProgress($milestoneId);\n} catch (\\Leantime\\Core\\Exceptions\\EntryNotFoundException $e) {\n    // milestone deleted or foreign — render placeholder instead of failing the dashboard\n    $percent = null;\n}","preventionTips":["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"],"tags":["tickets","milestones","entity-not-found","json-rpc","api"],"backgroundTag":"entity-not-found","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}