n8n-io/n8n · warning · NotFoundError
Workflow ${workflowId} does not exist.
Error message
Workflow ${workflowId} does not exist. What it means
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.
Source
Thrown at packages/cli/src/controllers/workflow-statistics.controller.ts:49
// TODO: move this into a new decorator `@ValidateWorkflowPermission`
@Middleware()
async hasWorkflowAccess(req: StatisticsRequest.GetOne, _res: Response, next: NextFunction) {
const { user } = req;
const workflowId = req.params.id;
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (workflow) {
next();
} else {
this.logger.warn('User attempted to read a workflow without permissions', {
workflowId,
userId: user.id,
});
// Otherwise, make and return an error
throw new NotFoundError(`Workflow ${workflowId} does not exist.`);
}
}
@Get('/:id/counts/')
async getCounts(req: StatisticsRequest.GetOne): Promise<WorkflowStatisticsData<number>> {
return await this.getData(req.params.id, 'count', 0);
}
@Get('/:id/times/')
async getTimes(req: StatisticsRequest.GetOne): Promise<WorkflowStatisticsData<Date | null>> {
return await this.getData(req.params.id, 'latestEvent', null);
}
@Get('/:id/data-loaded/')
async getDataLoaded(req: StatisticsRequest.GetOne): Promise<IWorkflowStatisticsDataLoaded> {
// Get flag
const workflowId = req.params.id;
View on GitHub (pinned to 5ac6606e81)
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.
Defensive patterns
Strategy: validation
Validate before calling
async function canReadWorkflow(workflowId: string) {
const r = await fetch(`/rest/workflows/${workflowId}`);
return r.ok; // 403/404 both surface as false
}
if (!(await canReadWorkflow(id))) {
throw new Error('Workflow missing or not accessible (workflow:read required)');
} Try / catch
try { await fetch(`/rest/workflow-statistics/${id}/counts`); }
catch (e) { if (e.statusCode === 404) { /* gone or no access; stop polling */ } else throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- 404
- User not found
- Target user not found
- Role not found
- User is missing a scope required to perform this action
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/a4eb232c129f7532.
Report an issue: GitHub.