Skip to content

Commit cfe80a4

Browse files
kristof-siketclaude
andcommitted
fix(env): scope branch-override lookup by branchId
`findVariableByNaturalKey` listed environment variables by `(projectId, class, key)` only and filtered by branch client-side, but the list endpoint is paginated (default 100 rows, ordered createdAt asc). Once a project accumulates more than a page of preview overrides for a key, the newest branch's row falls off the first page, so the lookup returns null even though the row exists. `project env update --branch` then reports the variable as not found, and `project env add --branch`'s pre-check also misses it and POSTs, which the server rejects with a 409. The two paths contradict each other and neither can touch an existing branch override. Pass `branchId` to the list query for branch-scoped lookups (the endpoint already supports the filter) so the server returns the single exact row regardless of how many branches exist. Role-scoped lookups are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f152fe3 commit cfe80a4

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

packages/cli/src/controllers/app-env-api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export async function findVariableByNaturalKey(
3333
resolved: ResolvedEnvApiScope,
3434
signal: AbortSignal,
3535
): Promise<RawEnvironmentVariable | null> {
36+
// Filter by branchId server-side for branch-scoped lookups. The list
37+
// endpoint is paginated (default 100 rows, createdAt asc); without this
38+
// filter a project with more than a page of preview overrides for `key`
39+
// pushes the newest branch's row off the first page, so the row this
40+
// lookup exists to find is silently invisible and add/update misfire.
3641
const { data, error, response } = await client.GET(
3742
"/v1/environment-variables",
3843
{
@@ -41,6 +46,9 @@ export async function findVariableByNaturalKey(
4146
projectId,
4247
class: resolved.apiTarget.class,
4348
key,
49+
...(resolved.apiTarget.branchId !== null
50+
? { branchId: resolved.apiTarget.branchId }
51+
: {}),
4452
},
4553
},
4654
signal,

packages/cli/tests/app-env.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,67 @@ describe("env update", () => {
944944
);
945945
});
946946

947+
it("scopes the branch-override lookup by branchId so it survives pagination", async () => {
948+
const client = createMockClient();
949+
client.envGET
950+
.mockResolvedValueOnce({
951+
data: {
952+
data: [makeBranchRow()],
953+
pagination: { hasMore: false, nextCursor: null },
954+
},
955+
response: { status: 200 },
956+
})
957+
.mockResolvedValueOnce({
958+
data: {
959+
data: [
960+
makeVariableRow({
961+
id: "envvar_branch",
962+
key: "DATABASE_URL",
963+
class: "preview",
964+
branchId: "br_feature",
965+
}),
966+
],
967+
pagination: { hasMore: false, nextCursor: null },
968+
},
969+
response: { status: 200 },
970+
});
971+
client.PATCH.mockResolvedValueOnce({
972+
data: {
973+
data: makeVariableRow({
974+
id: "envvar_branch",
975+
key: "DATABASE_URL",
976+
class: "preview",
977+
branchId: "br_feature",
978+
}),
979+
},
980+
response: { status: 200 },
981+
});
982+
983+
const { controllers, createTempCwd, createTestCommandContext } =
984+
await loadControllers(client, "proj_123");
985+
const cwd = await createTempCwd();
986+
await writeLocalPin(cwd);
987+
const { context } = await createTestCommandContext({ cwd });
988+
989+
await controllers.runEnvUpdate(context, "DATABASE_URL=postgresql://new", {
990+
branchName: "feature/foo",
991+
});
992+
993+
expect(client.GET).toHaveBeenCalledWith(
994+
"/v1/environment-variables",
995+
expect.objectContaining({
996+
params: {
997+
query: expect.objectContaining({
998+
projectId: "proj_123",
999+
class: "preview",
1000+
key: "DATABASE_URL",
1001+
branchId: "br_feature",
1002+
}),
1003+
},
1004+
}),
1005+
);
1006+
});
1007+
9471008
it("updates variables from a dotenv file via PATCH", async () => {
9481009
const client = createMockClient();
9491010
client.envGET

0 commit comments

Comments
 (0)