In multi-repo projects, preview environments from different repos collide on a shared pr-{N} environment and namespace.
Preview environment object names are repo-disambiguated via constants.PreviewEnvironmentName(repo, number, multiRepo), but the environment name is always fmt.Sprintf("pr-%d", pe.Spec.PullRequest.Number) (internal/controller/previewenvironment_controller.go:93, and the comparison at :909). PR numbers overlap across repos constantly, so two repos in one project with same-numbered open PRs produce two live PEs sharing environment pr-N and namespace pj-{project}-pr-N.
Damage: (a) ensureAppEnvOverride runs per-PE over every app in the control namespace; for an app in repo1, PE-A sets the branch override while PE-B (which does not target that repo) clears it, and each App update re-enqueues all PEs, producing an unbounded update loop that alternates rebuilds between the PR branch and the default branch; (b) cleanupPreview on the first PE closing removes the shared environment, strips overrides, and deletes the shared namespace while the other PE is still open.
Fix: include the repo slug in the environment name when multiRepo (mirroring the PE naming), or refcount environment/namespace cleanup so it only runs when the last PE referencing it closes.
Found during a Fable 5 quality audit of the codebase (same audit that produced #433). Verified against the code at the current main.
In multi-repo projects, preview environments from different repos collide on a shared
pr-{N}environment and namespace.Preview environment object names are repo-disambiguated via
constants.PreviewEnvironmentName(repo, number, multiRepo), but the environment name is alwaysfmt.Sprintf("pr-%d", pe.Spec.PullRequest.Number)(internal/controller/previewenvironment_controller.go:93, and the comparison at :909). PR numbers overlap across repos constantly, so two repos in one project with same-numbered open PRs produce two live PEs sharing environmentpr-Nand namespacepj-{project}-pr-N.Damage: (a)
ensureAppEnvOverrideruns per-PE over every app in the control namespace; for an app in repo1, PE-A sets the branch override while PE-B (which does not target that repo) clears it, and each App update re-enqueues all PEs, producing an unbounded update loop that alternates rebuilds between the PR branch and the default branch; (b)cleanupPreviewon the first PE closing removes the shared environment, strips overrides, and deletes the shared namespace while the other PE is still open.Fix: include the repo slug in the environment name when
multiRepo(mirroring the PE naming), or refcount environment/namespace cleanup so it only runs when the last PE referencing it closes.Found during a Fable 5 quality audit of the codebase (same audit that produced #433). Verified against the code at the current main.