From 8da5a0b44a320bcc13ad14ca570c371a43a9f2be Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 1 Jul 2026 21:24:38 +0000 Subject: [PATCH] fix(secrets): skip deleted optional user secrets Let optional user-secret bindings resolve to null when their definition has been deleted, matching the pre-dispatch gate's optional-binding behavior. Co-Authored-By: Paperclip --- server/src/__tests__/secrets-service.test.ts | 18 +++++++++++++++++- server/src/services/secrets.ts | 8 +++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/secrets-service.test.ts b/server/src/__tests__/secrets-service.test.ts index 26817f8f7d9..c2511c3e4d5 100644 --- a/server/src/__tests__/secrets-service.test.ts +++ b/server/src/__tests__/secrets-service.test.ts @@ -535,7 +535,23 @@ describeEmbeddedPostgres("secretService", () => { }); await db .update(userSecretDefinitions) - .set({ status: "active" }) + .set({ status: "deleted", deletedAt: new Date() }) + .where(eq(userSecretDefinitions.id, definition.id)); + await expect( + svc.resolveEnvBindings(companyId, optionalEnv, { + consumerType: "agent", + consumerId: "agent-optional", + actorType: "agent", + actorId: "agent-optional", + responsibleUserId: "user-2", + }), + ).resolves.toMatchObject({ + env: {}, + manifest: [], + }); + await db + .update(userSecretDefinitions) + .set({ status: "active", deletedAt: null }) .where(eq(userSecretDefinitions.id, definition.id)); const resolved = await svc.resolveEnvBindings(companyId, env, { diff --git a/server/src/services/secrets.ts b/server/src/services/secrets.ts index 23f8449a2b9..c2875e8033b 100644 --- a/server/src/services/secrets.ts +++ b/server/src/services/secrets.ts @@ -2252,8 +2252,14 @@ export function secretService(db: Db) { context?: SecretConsumerContext, ): Promise => { const responsibleUserId = input.responsibleUserId ?? context?.responsibleUserId ?? null; - const definition = await resolveUserSecretDefinition(companyId, input); const optionalBinding = input.allowMissingOverride || input.required === false; + let definition: typeof userSecretDefinitions.$inferSelect; + try { + definition = await resolveUserSecretDefinition(companyId, input); + } catch (error) { + if (optionalBinding && error instanceof HttpError && error.status === 404) return null; + throw error; + } if (definition.status !== "active") { if (optionalBinding) return null; throw unprocessable("User secret definition is not active");