Skip to content

Commit 5fab463

Browse files
d-csTrigger.dev RepoOps
authored andcommitted
chore(webapp): remove the preview auto-archive flag
Preview branch auto-archiving is now always on. The `previewAutoArchiveEnabled` feature flag is removed, along with the code paths that ran when it was off. Mono-RevId: de133ebb080e199c8dc4b13defe146c3e4db276b
1 parent 62db886 commit 5fab463

8 files changed

Lines changed: 14 additions & 175 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Preview branch auto-archiving is now always available, with no feature flag to enable first.

‎apps/webapp/app/presenters/v3/BranchesPresenter.server.ts‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { classifyPreviewBranch } from "~/utils/previewAutoArchive";
2-
import {
3-
previewBranchActivity,
4-
isPreviewAutoArchiveEnabled,
5-
} from "~/services/previewAutoArchive.server";
2+
import { previewBranchActivity } from "~/services/previewAutoArchive.server";
63
import { GitMeta } from "@trigger.dev/core/v3";
74
import { DEFAULT_DEV_BRANCH } from "@trigger.dev/core/v3/utils/gitBranch";
85
import { type RuntimeEnvironmentType } from "@trigger.dev/database";
@@ -113,7 +110,6 @@ export class BranchesPresenter {
113110
select: {
114111
id: true,
115112
organizationId: true,
116-
...(envType === "PREVIEW" ? { organization: { select: { featureFlags: true } } } : {}),
117113
},
118114
where: {
119115
slug: projectSlug,
@@ -131,12 +127,7 @@ export class BranchesPresenter {
131127
throw new Error("Project not found");
132128
}
133129

134-
const autoArchiveAvailable =
135-
envType === "PREVIEW" &&
136-
(await isPreviewAutoArchiveEnabled(
137-
this.#prismaClient,
138-
project.organization?.featureFlags ?? null
139-
));
130+
const autoArchiveAvailable = envType === "PREVIEW";
140131

141132
const branchableEnvironment = await this.#prismaClient.runtimeEnvironment.findFirst({
142133
select: {

‎apps/webapp/app/routes/resources.branches.auto-archive.tsx‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { rbac } from "~/services/rbac.server";
1010
import {
1111
previewAutoArchiveCount,
1212
savePreviewAutoArchivePolicy,
13-
isPreviewAutoArchiveEnabled,
1413
} from "~/services/previewAutoArchive.server";
1514
import { PreviewAutoArchivePolicy } from "~/utils/previewAutoArchive";
1615
import { Button } from "~/components/primitives/Buttons";
@@ -81,17 +80,10 @@ export async function action({ request }: ActionFunctionArgs) {
8180
id: true,
8281
organizationId: true,
8382
projectId: true,
84-
organization: { select: { featureFlags: true } },
8583
},
8684
});
8785
if (!parent)
8886
return json({ ok: false, error: "Preview environment not found" } as const, { status: 404 });
89-
if (!(await isPreviewAutoArchiveEnabled(prisma, parent.organization.featureFlags))) {
90-
return json(
91-
{ ok: false, error: "Preview auto-archive is not enabled for this organization." } as const,
92-
{ status: 403 }
93-
);
94-
}
9587
const auth = await rbac.authenticateSession(request, {
9688
userId,
9789
organizationId: parent.organizationId,

‎apps/webapp/app/services/previewAutoArchive.server.test.ts‎

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { tmpdir } from "node:os";
88
import { join } from "node:path";
99
import { fileURLToPath } from "node:url";
1010
import { $transaction } from "~/db.server";
11-
import { FeatureFlagCatalog, FEATURE_FLAG } from "~/v3/featureFlags";
1211
import { createDeploymentWithNextVersion } from "~/v3/services/initializeDeployment/createDeploymentWithNextVersion.server";
1312
import {
1413
PreviewAutoArchivePolicy,
@@ -23,82 +22,9 @@ import {
2322
processPreviewAutoArchivePage,
2423
previewAutoArchiveCount,
2524
savePreviewAutoArchivePolicy,
26-
isPreviewAutoArchiveEnabled,
2725
previewBranchActivity,
2826
} from "./previewAutoArchive.server";
2927

30-
postgresTest(
31-
"rollout defaults off with organization overrides over the global flag",
32-
async ({ prisma }) => {
33-
const key = FEATURE_FLAG.previewAutoArchiveEnabled;
34-
expect(FeatureFlagCatalog[key].safeParse("false").success).toBe(false);
35-
expect(await isPreviewAutoArchiveEnabled(prisma, null)).toBe(false);
36-
expect(await isPreviewAutoArchiveEnabled(prisma, { [key]: true })).toBe(true);
37-
await prisma.featureFlag.create({ data: { key, value: true } });
38-
expect(await isPreviewAutoArchiveEnabled(prisma, null)).toBe(true);
39-
expect(await isPreviewAutoArchiveEnabled(prisma, { [key]: false })).toBe(false);
40-
await prisma.featureFlag.update({ where: { key }, data: { value: false } });
41-
expect(await isPreviewAutoArchiveEnabled(prisma, null)).toBe(false);
42-
expect(await isPreviewAutoArchiveEnabled(prisma, { [key]: true })).toBe(true);
43-
}
44-
);
45-
46-
postgresTest(
47-
"disabled rollout preserves configured policies and resumes when globally enabled",
48-
async ({ prisma }) => {
49-
const { parent, branch } = await seed(prisma, false);
50-
const candidate = await branch();
51-
const page = await processPreviewAutoArchivePage(prisma, 0, now);
52-
expect(page).toMatchObject({ scanned: 0, archived: [] });
53-
const paused = await prisma.runtimeEnvironment.findFirstOrThrow({ where: { id: parent.id } });
54-
expect(paused.previewAutoArchiveAfterDays).toBe(14);
55-
expect(paused.previewAutoArchiveNextCheckAt).toEqual(new Date(now.getTime() + 3_600_000));
56-
await prisma.featureFlag.create({
57-
data: { key: FEATURE_FLAG.previewAutoArchiveEnabled, value: true },
58-
});
59-
const resumed = await processPreviewAutoArchivePage(
60-
prisma,
61-
0,
62-
paused.previewAutoArchiveNextCheckAt!
63-
);
64-
expect(resumed?.archived.map(({ id }) => id)).toEqual([candidate.id]);
65-
}
66-
);
67-
68-
postgresTest(
69-
"revoking rollout between pages pauses without losing the cursor",
70-
async ({ prisma }) => {
71-
const { parent, organization, branch } = await seed(prisma);
72-
for (let i = 0; i < 101; i++) await branch();
73-
expect((await processPreviewAutoArchivePage(prisma, 0, now))?.archived).toHaveLength(100);
74-
const before = await prisma.runtimeEnvironment.findFirstOrThrow({ where: { id: parent.id } });
75-
await prisma.organization.update({
76-
where: { id: organization.id },
77-
data: { featureFlags: { previewAutoArchiveEnabled: false } },
78-
});
79-
await prisma.featureFlag.create({
80-
data: { key: FEATURE_FLAG.previewAutoArchiveEnabled, value: true },
81-
});
82-
expect(await processPreviewAutoArchivePage(prisma, 0, now)).toMatchObject({
83-
scanned: 0,
84-
archived: [],
85-
});
86-
const paused = await prisma.runtimeEnvironment.findFirstOrThrow({ where: { id: parent.id } });
87-
expect(paused.previewAutoArchiveCursorId).toBe(before.previewAutoArchiveCursorId);
88-
expect(paused.previewAutoArchiveCursorCreatedAt).toEqual(
89-
before.previewAutoArchiveCursorCreatedAt
90-
);
91-
await prisma.organization.update({
92-
where: { id: organization.id },
93-
data: { featureFlags: {} },
94-
});
95-
expect(
96-
(await processPreviewAutoArchivePage(prisma, 0, paused.previewAutoArchiveNextCheckAt!))
97-
?.archived
98-
).toHaveLength(1);
99-
}
100-
);
101-
10228
const archiveGuard = {
10329
archiveGuard: {
10430
type: "PREVIEW" as const,
@@ -373,43 +299,6 @@ postgresTest(
373299
}
374300
);
375301

376-
postgresTest(
377-
"flag enabled during deployment preparation cannot bypass the archive lock",
378-
async ({ prisma }) => {
379-
const { branch, deployData, project } = await seed(prisma, false);
380-
await prisma.organization.update({
381-
where: { id: project.organizationId },
382-
data: { featureFlags: { previewAutoArchiveEnabled: false } },
383-
});
384-
const candidate = await branch();
385-
const preparing = barrier();
386-
const resume = barrier();
387-
const deploying = createDeploymentWithNextVersion(
388-
prisma,
389-
candidate.id,
390-
async () => {
391-
preparing.release();
392-
await resume.promise;
393-
return deployData();
394-
},
395-
archiveGuard
396-
);
397-
const rejected = expect(deploying).rejects.toMatchObject({ status: 409 });
398-
await preparing.promise;
399-
try {
400-
await prisma.organization.update({
401-
where: { id: project.organizationId },
402-
data: { featureFlags: { previewAutoArchiveEnabled: true } },
403-
});
404-
expect((await processPreviewAutoArchivePage(prisma, 0, now))?.archived).toHaveLength(1);
405-
} finally {
406-
resume.release();
407-
}
408-
await rejected;
409-
expect(await prisma.workerDeployment.count({ where: { environmentId: candidate.id } })).toBe(0);
410-
}
411-
);
412-
413302
postgresTest(
414303
"a policy update owns the root and prevents cleanup using an outdated policy",
415304
async ({ prisma }) => {

‎apps/webapp/app/services/previewAutoArchive.server.ts‎

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import {
1313
} from "~/utils/previewAutoArchive";
1414
import { archiveBranchesMutation } from "./branchArchiveMutation.server";
1515
import { logger } from "./logger.server";
16-
import { FEATURE_FLAG } from "~/v3/featureFlags";
17-
import { makeFlag } from "~/v3/featureFlags.server";
1816
import { trail } from "agentcrumbs"; // @crumbs
1917

2018
const crumb = trail("webapp"); // @crumbs
@@ -48,22 +46,6 @@ export async function previewBranchActivity(prisma: PrismaClientOrTransaction, i
4846
return new Map(rows.map((row) => [row.id, row]));
4947
}
5048

51-
export function isPreviewAutoArchiveEnabled(
52-
prisma: PrismaClientOrTransaction,
53-
organizationFlags: unknown
54-
) {
55-
return makeFlag(prisma)({
56-
key: FEATURE_FLAG.previewAutoArchiveEnabled,
57-
defaultValue: false,
58-
overrides:
59-
organizationFlags &&
60-
typeof organizationFlags === "object" &&
61-
!Array.isArray(organizationFlags)
62-
? (organizationFlags as Record<string, unknown>)
63-
: undefined,
64-
});
65-
}
66-
6749
type DuePolicy = {
6850
id: string;
6951
previewAutoArchiveAfterDays: number;
@@ -73,7 +55,6 @@ type DuePolicy = {
7355
previewAutoArchiveCursorId: string | null;
7456
projectDeletedAt: Date | null;
7557
organizationDeletedAt: Date | null;
76-
organizationFlags: Prisma.JsonValue;
7758
};
7859

7960
type ArchiveBranch = Pick<RuntimeEnvironment, "id" | "slug" | "branchName" | "createdAt">;
@@ -82,12 +63,11 @@ type ArchiveBranch = Pick<RuntimeEnvironment, "id" | "slug" | "branchName" | "cr
8263
function previewArchiveDueQuery(now: Date) {
8364
// Prisma cannot express FOR UPDATE SKIP LOCKED. Claim and read the next due policy
8465
// together so concurrent workers take different roots. OF e locks only the environment,
85-
// while the joins provide tenant deletion and rollout state without extra lookups.
66+
// while the joins provide tenant deletion state without extra lookups.
8667
return Prisma.sql`
8768
SELECT e.id, e."previewAutoArchiveAfterDays", e."previewAutoArchiveExcludedBranches",
8869
e."previewAutoArchiveNextCheckAt", e."previewAutoArchiveCursorCreatedAt", e."previewAutoArchiveCursorId",
89-
p."deletedAt" AS "projectDeletedAt", o."deletedAt" AS "organizationDeletedAt",
90-
o."featureFlags" AS "organizationFlags"
70+
p."deletedAt" AS "projectDeletedAt", o."deletedAt" AS "organizationDeletedAt"
9171
FROM "RuntimeEnvironment" e
9272
JOIN "Project" p ON p.id = e."projectId"
9373
JOIN "Organization" o ON o.id = e."organizationId"
@@ -232,16 +212,6 @@ export async function processPreviewAutoArchivePage(
232212
});
233213
return { parentId: parent.id, scanned: 0, candidates: 0, archived: [], complete: true };
234214
}
235-
if (!(await isPreviewAutoArchiveEnabled(tx, parent.organizationFlags))) {
236-
crumb("preview archive rollout disabled", { parentId: parent.id }); // @crumbs
237-
// Keep durable progress and policy for re-enablement, without rescanning a
238-
// disabled organization on every tick or starving other due projects.
239-
await tx.runtimeEnvironment.update({
240-
where: { id: parent.id },
241-
data: { previewAutoArchiveNextCheckAt: new Date(now.getTime() + HOUR_MS) },
242-
});
243-
return { parentId: parent.id, scanned: 0, candidates: 0, archived: [], complete: true };
244-
}
245215
const cutoff = new Date(
246216
now.getTime() - parent.previewAutoArchiveAfterDays * PREVIEW_AUTO_ARCHIVE_DAY_MS
247217
);

‎apps/webapp/app/v3/featureFlags.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export const FEATURE_FLAG = {
99
hasQueryAccess: "hasQueryAccess",
1010
hasLogsPageAccess: "hasLogsPageAccess",
1111
hasWebhooksAccess: "hasWebhooksAccess",
12-
previewAutoArchiveEnabled: "previewAutoArchiveEnabled",
1312
hasAiAccess: "hasAiAccess",
1413
hasDashboardAgentAccess: "hasDashboardAgentAccess",
1514
dashboardAgentTurnEvalsEnabled: "dashboardAgentTurnEvalsEnabled",
@@ -70,8 +69,6 @@ export const FeatureFlagCatalog = {
7069
[FEATURE_FLAG.hasQueryAccess]: z.coerce.boolean(),
7170
[FEATURE_FLAG.hasLogsPageAccess]: z.coerce.boolean(),
7271
[FEATURE_FLAG.hasWebhooksAccess]: z.coerce.boolean(),
73-
// Opt-in rollout; organization overrides the global default. Unset means off.
74-
[FEATURE_FLAG.previewAutoArchiveEnabled]: z.boolean(),
7572
/**
7673
* Opts an organization into API rate limit metrics while the webapp runs with
7774
* API_RATE_LIMIT_METRICS_ENABLED=allowlist. Read from the organization override only, on the

‎apps/webapp/app/v3/services/initializeDeployment/createDeploymentWithNextVersion.server.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ export async function createDeploymentWithNextVersion(
7171
const data = await buildData(version);
7272

7373
try {
74-
// Non-preview deployments keep the original query path, including no flag lookup.
74+
// Non-preview deployments keep the original query path.
7575
if (options.archiveGuard?.type !== "PREVIEW") {
7676
return await prisma.workerDeployment.create({ data: { ...data, environmentId, version } });
7777
}
78-
// All previews lock, even with rollout disabled: flags can change during preparation.
79-
// Build data (including registry calls) above, outside the short lock. The archive
78+
// Every preview locks, whether or not its parent has an auto-archive policy, because the
79+
// policy can change during preparation. Build data (including registry calls) above, outside the short lock. The archive
8080
// sweep takes this same lock before checking deployment activity.
8181
const deployment = await $transaction(
8282
prisma,

‎apps/webapp/test/fixtures/previewAutoArchive.ts‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,8 @@ export const archiveTestNow = new Date("2026-09-16T12:00:00Z");
44
export const archiveTestOld = new Date(archiveTestNow.getTime() - 20 * 24 * 60 * 60 * 1000);
55
const now = archiveTestNow;
66
const old = archiveTestOld;
7-
export async function seedPreviewArchive(prisma: PrismaClient, rolloutEnabled = true) {
7+
export async function seedPreviewArchive(prisma: PrismaClient) {
88
const { project, organization } = await createTestOrgProjectWithMember(prisma);
9-
if (rolloutEnabled) {
10-
await prisma.organization.update({
11-
where: { id: organization.id },
12-
data: { featureFlags: { previewAutoArchiveEnabled: true } },
13-
});
14-
}
159
const base = { projectId: project.id, organizationId: organization.id, type: "PREVIEW" as const };
1610
const parent = await prisma.runtimeEnvironment.create({
1711
data: {

0 commit comments

Comments
 (0)