{"record":{"id":"a4eb232c129f7532","repo":"n8n-io/n8n","slug":"workflow-workflowid-does-not-exist","errorCode":null,"errorMessage":"Workflow ${workflowId} does not exist.","messagePattern":"Workflow (.+?) does not exist\\.","errorType":"exception","errorClass":"NotFoundError","httpStatus":404,"severity":"warning","filePath":"packages/cli/src/controllers/workflow-statistics.controller.ts","lineNumber":49,"sourceCode":"\t// TODO: move this into a new decorator `@ValidateWorkflowPermission`\n\t@Middleware()\n\tasync hasWorkflowAccess(req: StatisticsRequest.GetOne, _res: Response, next: NextFunction) {\n\t\tconst { user } = req;\n\t\tconst workflowId = req.params.id;\n\n\t\tconst workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [\n\t\t\t'workflow:read',\n\t\t]);\n\n\t\tif (workflow) {\n\t\t\tnext();\n\t\t} else {\n\t\t\tthis.logger.warn('User attempted to read a workflow without permissions', {\n\t\t\t\tworkflowId,\n\t\t\t\tuserId: user.id,\n\t\t\t});\n\t\t\t// Otherwise, make and return an error\n\t\t\tthrow new NotFoundError(`Workflow ${workflowId} does not exist.`);\n\t\t}\n\t}\n\n\t@Get('/:id/counts/')\n\tasync getCounts(req: StatisticsRequest.GetOne): Promise<WorkflowStatisticsData<number>> {\n\t\treturn await this.getData(req.params.id, 'count', 0);\n\t}\n\n\t@Get('/:id/times/')\n\tasync getTimes(req: StatisticsRequest.GetOne): Promise<WorkflowStatisticsData<Date | null>> {\n\t\treturn await this.getData(req.params.id, 'latestEvent', null);\n\t}\n\n\t@Get('/:id/data-loaded/')\n\tasync getDataLoaded(req: StatisticsRequest.GetOne): Promise<IWorkflowStatisticsDataLoaded> {\n\t\t// Get flag\n\t\tconst workflowId = req.params.id;\n","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/cli/src/controllers/workflow-statistics.controller.ts#L31-L67","documentation":"Thrown in the WorkflowStatisticsController middleware (the workflow-existence/permissions gate) when workflowFinderService.findWorkflowForUser(workflowId,user,['workflow:read']) returns a falsy result. The 404 deliberately conflates 'missing' and 'no-permission' to avoid leaking existence; the warn log captures the real reason as 'User attempted to read a workflow without permissions'. HTTP 404.","triggerScenarios":"Calling GET /workflow-statistics/:id/counts or /times where :id does not exist OR exists but is not accessible to the user (no workflow:read on the owning project). Same applies to any route that runs this middleware.","commonSituations":"User switched projects/tenants and the cached workflow id is now inaccessible; workflow was deleted; id copied from another instance; permission scope missing after an RBAC migration.","solutions":["Verify the workflow exists and is shared with the user's project (workflow:read scope) via the workflows API.","If the workflow was deleted, stop polling statistics for it.","Request the workflow:read permission on the owning project if access is intended."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function canReadWorkflow(workflowId: string) {\n  const r = await fetch(`/rest/workflows/${workflowId}`);\n  return r.ok; // 403/404 both surface as false\n}\nif (!(await canReadWorkflow(id))) {\n  throw new Error('Workflow missing or not accessible (workflow:read required)');\n}","typeGuard":null,"tryCatchPattern":"try { await fetch(`/rest/workflow-statistics/${id}/counts`); }\ncatch (e) { if (e.statusCode === 404) { /* gone or no access; stop polling */ } else throw e; }","preventionTips":["Verify workflow:read on the owning project before polling statistics.","Stop polling when a workflow is deleted.","Do not assume 404 means transient — it usually means missing or forbidden."],"tags":["workflows","workflow-statistics","rbac","rest-api","not-found","authorization"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}