n8n-io/n8n · error

Bad Gateway

Error message

Bad Gateway

What it means

CATALOG ANOMALY: the literal 'Bad Gateway' does not appear anywhere in project.controller.ts (it actually lives in telemetry.controller.ts:45). At the attributed line 323 the real code is the addProjectUsers response path: `if (conflicts.length > 0) return res.status(409).json({ conflicts });`, and the surrounding catch (lines 325-329) only converts UnlicensedProjectRoleError into a 400 and rethrows everything else as a generic 500. So the meaningful error surface here is either a structured 409 conflicts response or an unhandled 500 from an unexpected backend error during bulk user-add.

Source

Thrown at packages/cli/src/controllers/project.controller.ts:323

					project: { id: project.id, name: project.name },
				});
			}

			const relations = await this.projectsService.getProjectRelations(projectId);
			this.eventService.emit('team-project-updated', {
				userId: req.user.id,
				role: req.user.role.slug,
				members: relations.map((r) => ({ userId: r.userId, role: r.role.slug })),
				projectId,
			});

			// Response semantics:
			// - If at least one user was added, return 201. When there are also conflicts, include them in the body.
			// - If no users were added but conflicts exist, return 409 with conflicts.
			if (added.length > 0) {
				return conflicts.length > 0 ? res.status(201).json({ conflicts }) : res.status(201).send();
			}
			if (conflicts.length > 0) return res.status(409).json({ conflicts });
			return res.status(200).send();
		} catch (e) {
			if (e instanceof UnlicensedProjectRoleError) {
				throw new BadRequestError(e.message);
			}
			throw e;
		}
	}

	@Patch('/:projectId/users/:userId')
	@ProjectScope('project:update')
	async changeProjectUserRole(
		req: AuthenticatedRequest,
		res: Response,
		@Param('projectId') projectId: string,
		@Param('userId') userId: string,
		@Body body: ChangeUserRoleInProject,
	) {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Inspect the response status and body: 201 with {conflicts}, 409 with {conflicts}, or 500 (rethrown).
  2. For 409, resolve each conflict (e.g. user already a member, role mismatch) and retry the remaining users.
  3. For 500, check server logs for the rethrown root cause — do not retry unchanged.
Defensive patterns

Strategy: try-catch

Validate before calling

// Differentiate the documented response shapes before treating as error.
function classifyAddUsersResponse(res) {
  if (res.status === 201 || res.status === 200) return 'ok';
  if (res.status === 409) return 'conflicts'; // res.body.conflicts[]
  return 'error';
}

Type guard

function hasConflicts(body) {
  return body && Array.isArray(body.conflicts);
}

Try / catch

try {
  const res = await api.post(`/projects/${projectId}/users`, payload);
  if (res.status === 409 && res.data?.conflicts) {
    // resolve per-user conflicts, retry the subset that can be added
  }
} catch (e) {
  if (e.status === 500) {
    // inspect server logs; do not retry identical payload
  } else if (e.status === 400 && /not licensed to use role/.test(e.message)) {
    // switch to a licensed role
  } else { throw e; }
}

Prevention

When it happens

Trigger: POST /:projectId/users where some users cannot be added (returns 409 with a conflicts body), or where an unexpected non-UnlicensedProjectRole error escapes the catch and surfaces as 500 (DB constraint, missing role, FK violation).

Common situations: Adding users who are already members, or whose target role is invalid, producing conflicts; a transient DB/backend failure during the bulk add; the body must be inspected to distinguish 'partial success with conflicts' from a hard failure.

Understand the failure class

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/968b34a46d88e84c. Report an issue: GitHub.