Skip to content

feat(webapp): Vercel integration#2994

Draft
0ski wants to merge 42 commits intomainfrom
oskar/feat-vercel-integration
Draft

feat(webapp): Vercel integration#2994
0ski wants to merge 42 commits intomainfrom
oskar/feat-vercel-integration

Conversation

@0ski
Copy link
Collaborator

@0ski 0ski commented Feb 3, 2026

Vercel integration

Desc + Vid coming soon

For human reviewer:

  • check the db schema
  • check if posthog user attribution call is correct (telemetry.server.ts & referralSource)

@changeset-bot
Copy link

changeset-bot bot commented Feb 3, 2026

🦋 Changeset detected

Latest commit: d23d10d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 28 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@internal/sdk-compat-tests Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

Walkthrough

Adds a Vercel integration end-to-end: database migrations and Prisma schema updates (integration tables, deployment tracking, env var versioning); new server-side modules and services for OAuth state, token exchange, Vercel API repository, and integration service; presenters and API routes for settings, onboarding, connect/install/callback flows, per-resource management, and project integrations; UI components (onboarding modal, settings panel, logos, build-settings, menu item, modals, checkboxes); environment/schema updates and referral/telemetry helpers; environment variable repository changes with versioning and lastUpdatedBy; path helpers, tests, and an added @vercel/sdk dependency.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete. It lacks detailed explanation of changes, testing steps, and changelog details required by the template. Only a placeholder note with 'Desc + Vid coming soon' and reviewer guidance is provided. Complete the description by filling out all template sections: provide a comprehensive changelog of what was changed, explain the implementation details, document testing steps, and include any relevant screenshots or videos.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(webapp): Vercel integration' clearly and concisely describes the main change—adding Vercel integration to the webapp. It follows the conventional format and is specific enough to understand the primary purpose.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch oskar/feat-vercel-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

183-210: ⚠️ Potential issue | 🟠 Major

Don’t skip isSecret changes when the value is unchanged.

If options.isSecret is provided (e.g., overriding to secret) and the secret value matches, the early continue prevents updating isSecret (and skips creation when a value record is missing). This breaks “mark as secret” with unchanged values.

🔧 Proposed fix
-            // Check if value already exists and is the same - skip update if unchanged
-            const existingSecret = await secretStore.getSecret(SecretValue, key);
-            if (existingSecret && existingSecret.secret === variable.value) {
-              // Value is unchanged, skip this variable for this environment
-              continue;
-            }
-
-            const existingValueRecord = await tx.environmentVariableValue.findFirst({
+            const existingValueRecord = await tx.environmentVariableValue.findFirst({
               where: {
                 variableId: environmentVariable.id,
                 environmentId,
               },
             });
+
+            // Check if value already exists and is the same - skip update if unchanged *and*
+            // there is no metadata change (e.g. isSecret toggle)
+            const existingSecret = await secretStore.getSecret(SecretValue, key);
+            const canSkip =
+              existingSecret &&
+              existingSecret.secret === variable.value &&
+              existingValueRecord &&
+              (options.isSecret === undefined ||
+                existingValueRecord.isSecret === options.isSecret);
+            if (canSkip) {
+              continue;
+            }
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 323-324: Remove the debug console.log call in
VercelOnboardingModal and either delete the line or replace it with your app's
proper logging utility (e.g., processLogger.info or useLogger().info) before
calling onDataReload(); specifically remove the line containing
console.log("Vercel onboarding: Reloading data...") in the block that calls
onDataReload() so production code has no console.log usage and any message uses
the established logger API.

In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 22-48: The isVercelSecretType function currently returns true only
for "secret" and "sensitive", so Vercel env vars with type "encrypted" are
treated as non-secret; update isVercelSecretType to also return true for
"encrypted" (i.e., treat "encrypted" as a secret-intended type) so secret
detection in sync logic (isVercelSecretType) correctly classifies encrypted
variables as secrets.

In `@apps/webapp/app/presenters/v3/BranchesPresenter.server.ts`:
- Line 45: The GitMeta schema's source enum is missing "trigger_vercel_app",
causing GitMeta.safeParse in processGitMetadata to reject Vercel-origin
metadata; update the GitMeta z.enum definition in the common schema (the one
referenced by GitMeta.safeParse and used alongside GitMetaLinks) to include
"trigger_vercel_app" alongside "trigger_github_app", "github_actions", and
"local" so Vercel-sourced git metadata is validated and preserved.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:
- Around line 650-651: The delete Button currently renders an empty label when
idle ({isLoading ? "Deleting" : ""}), making the action undiscoverable; update
the Button's children to show a default label (e.g., {isLoading ? "Deleting" :
"Delete"}) and add an explicit accessible name if missing (aria-label="Delete
variable" or similar) on the same Button instance to preserve clarity while
loading and improve accessibility.
- Around line 553-555: The menu Button (component named Button with props
LeadingIcon={PencilSquareIcon}) is rendering empty because it has no child/text;
restore the visible label by adding the string "Edit" as the Button's child (or
use the Button's text prop if that pattern is used elsewhere) so the menu item
shows "Edit" alongside the PencilSquareIcon in route.tsx where the Button is
declared.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:
- Line 315: The fetcher is typed as any—replace useTypedFetcher<any>() with a
correctly typed fetcher by importing the loader (response) type from the Vercel
resource route and using it as the generic parameter; e.g., import the loader's
Response/LoaderData type used by that route and change the declaration of
vercelFetcher (the call to useTypedFetcher) to use that specific type instead of
any so TypeScript enforces the actual data shape.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx:
- Around line 118-120: ActionSchema is defined but never used; update the
route's action function to parse and validate the incoming form data with
ActionSchema (e.g. call await request.formData(), extract the "intent" value,
and use ActionSchema.parse or safeParse). If validation fails return a 400
response or handle the error path; on success proceed with the existing
uninstall logic. Reference ActionSchema and the action function exported from
this route when making the change.
- Around line 350-354: There is a duplicate empty-state branch: the conditional
rendering checking connectedProjects.length === 0 that returns a <TableBlankRow>
is unreachable because an earlier check already renders a different empty state;
remove the redundant block (the conditional containing TableBlankRow) so only
the original empty-state UI remains and ensure no other JSX references the
removed fragment (look for connectedProjects and TableBlankRow usages to locate
the block).

In
`@apps/webapp/app/routes/api.v1.orgs`.$organizationSlug.projects.$projectParam.vercel.projects.ts:
- Around line 27-71: The loader currently returns Vercel integration metadata
without authenticating or checking org membership; call
authenticateApiRequestWithPersonalAccessToken at the start of loader (after the
OPTIONS/CORS handling) to authenticate the requester, then verify the
authenticated user's membership in the organization identified by
organizationSlug (use the same membership-check logic/pattern used in
api.v1.orgs.$orgParam.projects.ts — e.g., lookup organization membership via
prisma using the authenticated user's id and organizationSlug) and return a
403/json error via apiCors if not a member; only after authentication and
membership check proceed to call
VercelIntegrationService.getVercelProjectIntegration and return the integration
details.

In `@apps/webapp/app/routes/login.mfa/route.tsx`:
- Around line 166-190: The session.unset(...) changes to the user session are
not being persisted because only authSession is committed; after you call
session.unset for the pending-mfa-* fields (the session object used for the
logged-in user), call await sessionStorage.commitSession(session) and append its
Set-Cookie header (e.g. headers.append("Set-Cookie", await
sessionStorage.commitSession(session))) so the cleared pending-mfa-* values are
written to the cookie; ensure you commit this user session before
returning/setting other cookies (keep existing authSession commit intact).

In
`@apps/webapp/app/routes/resources.environments`.$environmentId.regenerate-api-key.tsx:
- Around line 27-33: The call to syncApiKeyToVercelInBackground is
fire-and-forget and may be terminated in serverless runtimes; change the code to
await syncApiKeyToVercelInBackground(...) inside a try/catch so the request
waits for completion and logs any errors (use updatedEnvironment.projectId,
updatedEnvironment.type, updatedEnvironment.apiKey), or alternatively dispatch
the same payload to a durable background job/queue and await enqueue
confirmation; ensure you do not leave an unhandled promise and add error
handling/logging around the call.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 772-773: The envVarsConfigLink prop is missing the environment
scope; update the usage of envVarsConfigLink (where it's currently built from
organizationSlug and projectSlug) to include the environmentSlug from the loader
so the path becomes the canonical
/orgs/{organizationSlug}/projects/{projectSlug}/env/{environmentSlug}/environment-variables;
locate the JSX/prop that sets envVarsConfigLink and insert environmentSlug
between projectSlug and environment-variables (using the existing
environmentSlug variable).

In `@apps/webapp/app/routes/vercel.callback.ts`:
- Around line 9-18: The VercelCallbackSchema currently declares configurationId
as required (VercelCallbackSchema -> configurationId: z.string()) but the
handler logic later treats configurationId as optional (checks if
(configurationId)); make these consistent by updating the schema to
configurationId: z.string().optional() so the parsed payload matches the
conditional, and keep the existing runtime check, or alternatively remove the
runtime if (configurationId) guard and leave the schema required—choose the
former if the code must accept callbacks without configurationId.

In `@apps/webapp/app/routes/vercel.onboarding.tsx`:
- Line 268: The isInstalling loading flag (useState in this file) is set true on
form submit but never reset on error; update the submit flow that calls
setIsInstalling(true) so it also sets setIsInstalling(false) when the action
returns an error or throws—e.g., after awaiting the action response (or in the
catch/finally) inspect actionData or the thrown error and call
setIsInstalling(false) to unblock the UI; reference the isInstalling state and
setIsInstalling function and the form submit/submit handler where you currently
set it true to add this reset.

In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 398-408: The code building updatedData ignores
params.syncEnvVarsMapping so onboarding selections aren't persisted; update the
construction of updatedData (VercelProjectIntegrationData) to set
syncEnvVarsMapping using params.syncEnvVarsMapping when provided (e.g.,
syncEnvVarsMapping: params.syncEnvVarsMapping ??
existing.parsedIntegrationData.syncEnvVarsMapping) instead of always copying
existing.parsedIntegrationData.syncEnvVarsMapping so new mappings from params
are saved.

In `@apps/webapp/app/v3/vercel/index.ts`:
- Around line 3-14: getVercelInstallParams currently returns the raw next query
param; update it to sanitize the next value at the source by importing and using
sanitizeRedirectPath (same helper used by other routes) and only include next in
the returned object if sanitizeRedirectPath(next) yields a truthy/safe value;
keep existing checks on code/configurationId/integration and return null
otherwise.

In
`@internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql`:
- Around line 14-18: Move the two index creations out of the current migration
and into a new, separate migration file and create them with CONCURRENTLY to
avoid table locks: remove the CREATE INDEX
"IntegrationDeployment_deploymentId_idx" and CREATE INDEX
"IntegrationDeployment_commitSHA_idx" statements from the current migration and
add a new migration containing CREATE INDEX CONCURRENTLY
"IntegrationDeployment_deploymentId_idx" ON
"public"."IntegrationDeployment"("deploymentId"); and CREATE INDEX CONCURRENTLY
"IntegrationDeployment_commitSHA_idx" ON
"public"."IntegrationDeployment"("commitSHA");; ensure the new migration is
executed non-transactionally (since CONCURRENTLY cannot run inside a
transaction) and update any migration metadata accordingly.

In `@internal-packages/database/prisma/schema.prisma`:
- Around line 1861-1862: The inline indexes for IntegrationDeployment
(IntegrationDeployment_commitSHA_idx and IntegrationDeployment_deploymentId_idx)
must be removed from the model (remove the @@index([...]) entries in the
IntegrationDeployment model in schema.prisma) and instead added in a separate
SQL migration file that runs outside a transaction and uses CREATE INDEX
CONCURRENTLY for both indexes (mirror how WorkerDeployment_commitSHA_idx and
OrganizationIntegration_externalOrganizationId_idx were separated). Update the
migration named 20260129162810_add_integration_deployment by removing any
bundled index creation and create a new migration that executes "CREATE INDEX
CONCURRENTLY ..." for IntegrationDeployment_commitSHA_idx and
IntegrationDeployment_deploymentId_idx so the table is not locked during
deployment.

In `@packages/core/src/v3/schemas/api.ts`:
- Around line 1155-1160: Add a changeset documenting the schema modification to
the discriminated union in packages/core (the source schema defined with
z.discriminatedUnion("type", ...) in packages/core/src/v3/schemas/api.ts); run
`pnpm run changeset:add`, select the packages/core package, write a brief
description that this change adds/modifies the optional "source" discriminated
union (user vs integration) and mark it as a patch release, then commit the
generated changeset file with the PR.
🧹 Nitpick comments (23)
.vscode/settings.json (1)

10-11: Consider team impact of experimental settings.

Enabling typescript.experimental.useTsgo may cause inconsistent IDE behavior across team members with different VS Code versions. Consider moving experimental/personal preferences to user settings rather than workspace settings, or document the requirement in the README.

apps/webapp/app/components/integrations/VercelLogo.tsx (1)

1-12: Consider adding accessibility attributes.

The SVG lacks accessibility attributes. For decorative icons, add aria-hidden="true" to hide from screen readers. If the icon conveys meaning, add role="img" and an aria-label.

♿ Proposed accessibility improvement
 export function VercelLogo({ className }: { className?: string }) {
   return (
     <svg
       viewBox="0 0 76 65"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
       className={className}
+      aria-hidden="true"
     >
       <path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
     </svg>
   );
 }
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx (1)

540-544: Consider hiding the Cancel button when preventDismiss is true.

The DialogClose Cancel button can still close the modal even when preventDismiss is enabled. If the intent is to force users to complete the action, consider conditionally hiding or disabling this button:

💡 Conditional Cancel button
               cancelButton={
-                  <DialogClose asChild>
-                    <Button variant="tertiary/medium">Cancel</Button>
-                  </DialogClose>
+                  !preventDismiss ? (
+                    <DialogClose asChild>
+                      <Button variant="tertiary/medium">Cancel</Button>
+                    </DialogClose>
+                  ) : undefined
               }

Note: If explicit cancellation is intentionally allowed while blocking accidental dismissal, this change is not needed.

apps/webapp/app/services/org.server.ts (1)

15-17: Consider using JSON error response for consistency.

Other API routes in the codebase return JSON error responses (e.g., json({ error: "..." }, { status: 404 })). Using a plain text Response here may be inconsistent with the rest of the application.

Suggested change
+import { json } from "@remix-run/node";
+
 export async function requireOrganization(request: Request, organizationSlug: string) {
   // ...
   if (!organization) {
-    throw new Response("Organization not found", { status: 404 });
+    throw json({ error: "Organization not found" }, { status: 404 });
   }
apps/webapp/app/routes/vercel.configure.tsx (1)

16-18: Use safeParse for better error handling.

Using parse directly will throw an unhandled error, resulting in a 500 response. Consider using safeParse to return a proper 400 response for invalid/missing parameters.

Suggested improvement
 export const loader = async ({ request }: LoaderFunctionArgs) => {
   const url = new URL(request.url);
   const searchParams = Object.fromEntries(url.searchParams);
   
-  const { configurationId } = SearchParamsSchema.parse(searchParams);
+  const parsed = SearchParamsSchema.safeParse(searchParams);
+  
+  if (!parsed.success) {
+    throw new Response("Missing or invalid configurationId", { status: 400 });
+  }
+  
+  const { configurationId } = parsed.data;
apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts (1)

73-82: Rename iterator variable for clarity.

The variable id is misleading since it represents an IntegrationDeployment record, not an ID. This makes id.id on line 76 particularly confusing.

Suggested improvement
     integrationDeployments:
       deployment.integrationDeployments.length > 0
-        ? deployment.integrationDeployments.map((id) => ({
-            id: id.id,
-            integrationName: id.integrationName,
-            integrationDeploymentId: id.integrationDeploymentId,
-            commitSHA: id.commitSHA,
-            createdAt: id.createdAt,
+        ? deployment.integrationDeployments.map((integrationDeployment) => ({
+            id: integrationDeployment.id,
+            integrationName: integrationDeployment.integrationName,
+            integrationDeploymentId: integrationDeployment.integrationDeploymentId,
+            commitSHA: integrationDeployment.commitSHA,
+            createdAt: integrationDeployment.createdAt,
           }))
         : undefined,
apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx (1)

118-124: Consider linking to a generic integrations page.

The menu item is labeled "Integrations" (plural) but links directly to the Vercel-specific page (organizationVercelIntegrationPath). If additional integrations are planned, consider linking to organizationIntegrationsPath(organization) instead, which would be the parent /settings/integrations route. This would allow for a landing page listing all available integrations.

If Vercel is intended to be the only integration for now, this is acceptable, but the naming could be misleading to users.

apps/webapp/app/utils/pathBuilder.ts (1)

170-178: Duplication of vercelResourcePath exists but may be contextually justified.

Two versions exist: pathBuilder.ts expects typed objects (OrgForPath, ProjectForPath, EnvironmentForPath with slug properties), while the vercel.tsx version accepts raw strings (organizationSlug, projectSlug, environmentSlug). The vercel.tsx route receives route params as strings and has no need to construct full typed objects just to extract their slug property. While the duplication is real, consolidating toward the pathBuilder version would require unnecessary object construction in the route handler. Consider this acceptable duplication given the different input scenarios, or add a comment explaining why the local version exists.

apps/webapp/app/models/orgIntegration.server.ts (1)

101-106: Consider URL-encoding the state parameter.

The state parameter is passed directly into the URL without encoding. If the base64-encoded state contains URL-unsafe characters like +, /, or =, this could cause issues with the OAuth callback.

♻️ Suggested fix
   static vercelInstallUrl(state: string): string {
     // The user goes to Vercel's marketplace to install the integration
     // After installation, Vercel redirects to our callback with the authorization code
     const redirectUri = encodeURIComponent(`${env.APP_ORIGIN}/vercel/callback`);
-    return `https://vercel.com/integrations/${env.VERCEL_INTEGRATION_APP_SLUG}/new?state=${state}&redirect_uri=${redirectUri}`;
+    return `https://vercel.com/integrations/${env.VERCEL_INTEGRATION_APP_SLUG}/new?state=${encodeURIComponent(state)}&redirect_uri=${redirectUri}`;
   }
apps/webapp/app/routes/auth.github.callback.tsx (1)

59-77: Duplicate user query could be consolidated.

The user is queried twice: once at lines 24-32 (selecting only id and mfaEnabledAt) and again at lines 61-63 (full user). Consider fetching createdAt in the first query to avoid the redundant database call.

♻️ Suggested optimization
   const userRecord = await prisma.user.findFirst({
     where: {
       id: auth.userId,
     },
     select: {
       id: true,
       mfaEnabledAt: true,
+      createdAt: true,
+      email: true,
+      name: true,
+      admin: true,
     },
   });

Then reuse userRecord instead of querying again:

   const referralSource = await getReferralSource(request);
   if (referralSource) {
-    const user = await prisma.user.findUnique({
-      where: { id: auth.userId },
-    });
-    if (user) {
-      const userAge = Date.now() - user.createdAt.getTime();
+    if (userRecord) {
+      const userAge = Date.now() - userRecord.createdAt.getTime();
       const isNewUser = userAge < 30 * 1000;
       
       if (isNewUser) {
         telemetry.user.identify({
-          user,
+          user: userRecord,
           isNewUser: true,
           referralSource,
         });
       }
     }
     headers.append("Set-Cookie", await clearReferralSourceCookie());
   }
apps/webapp/app/routes/magic.tsx (1)

58-79: Consider extracting referral/telemetry handling into a shared helper.

The same block now appears in magic login, MFA completion, and Google callback. A shared helper would reduce duplication and keep behavior consistent.

apps/webapp/app/routes/vercel.connect.tsx (1)

20-63: Avoid the redundant project lookup.

project (with organization) is already fetched in the loader; consider passing it into createOrFindVercelIntegration to save a DB round‑trip.

apps/webapp/app/routes/vercel.callback.ts (1)

5-5: Unused import: requireUserId.

The requireUserId function is imported but never used in this file. Only getUserId is called.

🧹 Remove unused import
-import { getUserId, requireUserId } from "~/services/session.server";
+import { getUserId } from "~/services/session.server";
apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts (2)

78-92: Consider extracting the type guard for cleaner code.

The inline type guard for lastUpdatedBy is verbose. A named type guard function would improve readability and reusability.

♻️ Extract type guard
function isUserUpdater(
  value: unknown
): value is { type: "user"; userId: string } {
  return (
    value !== null &&
    typeof value === "object" &&
    "type" in value &&
    value.type === "user" &&
    "userId" in value &&
    typeof value.userId === "string"
  );
}

// Then use:
const userIds = new Set(
  environmentVariables
    .flatMap((envVar) => envVar.values)
    .map((value) => value.lastUpdatedBy)
    .filter(isUserUpdater)
    .map((lastUpdatedBy) => lastUpdatedBy.userId)
);

141-151: Service instantiation reduces testability.

Creating VercelIntegrationService inline makes the presenter harder to test in isolation. Consider injecting it through the constructor or as a parameter.

♻️ Inject service for testability
 export class EnvironmentVariablesPresenter {
   `#prismaClient`: PrismaClient;
+  `#vercelService`: VercelIntegrationService;

-  constructor(prismaClient: PrismaClient = prisma) {
+  constructor(
+    prismaClient: PrismaClient = prisma,
+    vercelService?: VercelIntegrationService
+  ) {
     this.#prismaClient = prismaClient;
+    this.#vercelService = vercelService ?? new VercelIntegrationService(prismaClient);
   }

   // Then use this.#vercelService instead of creating new instance

As per coding guidelines: "Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts"

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (1)

343-381: Multiple overlapping useEffects for modal state create complexity.

Three separate useEffects (lines 343-361, 365-371, 374-381) all manage modal opening based on query params. This creates hard-to-follow control flow and potential for race conditions.

Consider consolidating into a single effect or using a state machine pattern.

♻️ Consolidate modal state management
// Single effect to manage modal state based on query param and data
useEffect(() => {
  if (!vercelIntegrationEnabled) return;
  
  if (hasQueryParam) {
    // Query param present - ensure modal is open and data is loaded
    if (!isModalOpen) {
      setIsModalOpen(true);
    }
    if (vercelFetcher.state === "idle" && !vercelFetcher.data?.onboardingData) {
      vercelFetcher.load(
        `${vercelResourcePath(organization.slug, project.slug, environment.slug)}?vercelOnboarding=true`
      );
    }
  } else if (isModalOpen) {
    // Query param removed - close modal
    setIsModalOpen(false);
  }
}, [hasQueryParam, vercelIntegrationEnabled, isModalOpen, vercelFetcher.state, vercelFetcher.data?.onboardingData]);
apps/webapp/app/routes/vercel.onboarding.tsx (1)

290-301: Duplicated URL construction logic.

The newOrgUrl and newProjectUrl construction share similar logic for building params with code, configurationId, integration, and next. Consider extracting a helper.

♻️ Extract URL builder helper
function buildIntegrationParams(data: { 
  code: string; 
  configurationId: string | null; 
  next: string | null;
  organizationId?: string;
}): URLSearchParams {
  const params = new URLSearchParams();
  params.set("code", data.code);
  if (data.configurationId) params.set("configurationId", data.configurationId);
  params.set("integration", "vercel");
  if (data.organizationId) params.set("organizationId", data.organizationId);
  if (data.next) params.set("next", data.next);
  return params;
}

Also applies to: 363-375

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (2)

434-434: Unused nextUrl in dependency array.

The handleSkipOnboarding callback includes nextUrl in its dependency array but doesn't use it in the function body.

🧹 Remove unused dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);

66-77: Complex state machine implemented with multiple useEffects.

The onboarding flow uses ~10 useEffects to manage state transitions. This creates implicit control flow that's difficult to trace and test. Consider using a dedicated state machine library (like XState) or consolidating into a reducer pattern.

This is a significant refactor but would greatly improve maintainability. The current implementation works but the implicit dependencies between effects make it fragile to future changes.

Also applies to: 119-329

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (2)

7-7: Unused import: useLoaderData.

useLoaderData is imported but useTypedLoaderData is used instead.

🧹 Remove unused import
-import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react";
+import { Form, useActionData, useNavigation } from "@remix-run/react";

85-87: Avoid any cast for integration data.

Casting integrationData to any loses type safety. Define a proper type for the Vercel integration data structure.

🔧 Add proper typing
+type VercelIntegrationData = {
+  teamId?: string;
+  installationId?: string;
+};

 // Get team ID from integrationData
-const integrationData = vercelIntegration.integrationData as any;
+const integrationData = vercelIntegration.integrationData as VercelIntegrationData | null;
 const teamId = integrationData?.teamId ?? null;
 const installationId = integrationData?.installationId ?? null;
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (2)

3-6: Derive ALL_ENV_SLUGS from EnvSlugSchema to prevent drift.

This keeps the constant and schema aligned if new slugs are added.

♻️ Proposed change
-export const ALL_ENV_SLUGS: EnvSlug[] = ["dev", "stg", "prod", "preview"];
+export const ALL_ENV_SLUGS: EnvSlug[] = EnvSlugSchema.options;

125-149: Consider centralizing env type/slug mapping.

envTypeToSlug overlaps with envSlug in apps/webapp/app/models/api-key.server.ts. Consolidating into a shared helper reduces drift risk.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/webapp/app/models/orgIntegration.server.ts`:
- Around line 99-113: The vercelInstallUrl method interpolates a Base64 `state`
into the query string without URL-encoding, which can corrupt values containing
+, /, =; update static vercelInstallUrl(state: string) to URL-encode the `state`
(e.g., via encodeURIComponent) before building the returned URL (also keep the
existing redirectUri encoding), referencing the vercelInstallUrl method and
env.VERCEL_INTEGRATION_APP_SLUG to locate the change.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cccb4e2 and 13489c6.

📒 Files selected for processing (2)
  • apps/webapp/app/models/orgIntegration.server.ts
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Organize services in the webapp following the pattern app/v3/services/*/*.server.ts

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
🧠 Learnings (3)
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/models/orgIntegration.server.ts
🧬 Code graph analysis (1)
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (1)
apps/webapp/app/models/orgIntegration.server.ts (1)
  • isIntegrationForService (50-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (1)

24-28: Good guard for Slack-only integrations.
Using isIntegrationForService prevents a non‑Slack integration from being used when a mismatched integrationId is supplied.

Also applies to: 648-654

apps/webapp/app/models/orgIntegration.server.ts (1)

50-55: Type guard looks solid.
Clear narrowing for service-specific integrations and keeps call sites clean.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/v3/schemas/api.ts (1)

680-724: ⚠️ Potential issue | 🟠 Major

Allow integration deployment commitSHA to be nullish to avoid response validation failures.

GetDeploymentResponseBody.commitSHA is already nullish, but integrationDeployments[].commitSHA is required. If an integration deployment lacks a commit (common for some Vercel builds or non‑git sources), the response will fail Zod validation and the API will 500. Consider aligning with the top‑level field.

🔧 Suggested adjustment
-        commitSHA: z.string(),
+        commitSHA: z.string().nullish(),
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 330-341: The useEffect that transitions state from
"loading-env-mapping" reads fromMarketplaceContext but doesn't include it in the
dependency array, which can cause stale closures; update the dependency array
for that useEffect to include fromMarketplaceContext (alongside state,
onboardingData, hasStagingEnvironment) so the effect re-runs when
fromMarketplaceContext changes and continue to use setState("env-mapping") /
setState("loading-env-vars") as before.
- Around line 992-994: The prop cast hides a true type mismatch between
onboardingData.gitHubAppInstallations (typed GitHubAppInstallationForVercel from
VercelSettingsPresenter) and the ConnectGitHubRepoModal prop type
(GitHubAppInstallation); fix by unifying the type definitions — either export
and reuse a single shared type (e.g., rename GitHubAppInstallationForVercel to
GitHubAppInstallation and use it in VercelSettingsPresenter and the GitHub
route) or update ConnectGitHubRepoModal to accept the presenter type; then
remove the unsafe `as GitHubAppInstallation[]` cast and update all affected
imports/exports so the types align without assertions.

In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 68-75: The VercelEnvironmentVariable type is missing the
customEnvironmentIds property which getVercelEnvironmentVariables returns and
the onboarding presenter expects; update the VercelEnvironmentVariable type (the
exported type definition named VercelEnvironmentVariable) to include
customEnvironmentIds?: string[] (or string[] if you want it required) so callers
like getVercelEnvironmentVariables and the onboarding presenter can use it
without excess‑property errors or any casts.

In
`@apps/webapp/app/routes/resources.environments`.$environmentId.regenerate-api-key.tsx:
- Around line 54-65: The background sync ignores non-throwing failures from
VercelIntegrationRepository.syncSingleApiKeyToVercel; update
syncApiKeyToVercelInBackground to inspect the returned result (e.g., const
result = await VercelIntegrationRepository.syncSingleApiKeyToVercel(...)) and if
result.success is false, call your logger (or processLogger) to log a
descriptive error including result.error and context (projectId,
environmentType) so non-exception failures are visible; still keep the try/catch
for thrown errors.

In `@apps/webapp/app/services/referralSource.server.ts`:
- Around line 16-23: The getReferralSource function currently performs a manual
equality check after parsing the cookie; instead validate the parsed value using
the project's Zod schema so unexpected values are rejected. Update logic in
getReferralSource to call referralSourceCookie.parse(cookie) and then validate
the result against the ReferralSource Zod schema (or have
referralSourceCookie.parse already return the Zod-validated value) and only
return the typed ReferralSource when validation succeeds; otherwise return null.
Ensure you reference and use the existing referralSourceCookie and the
ReferralSource schema/type so the function returns a correctly validated
ReferralSource | null.
🧹 Nitpick comments (4)
apps/webapp/app/routes/vercel.onboarding.tsx (1)

283-289: window.close() may silently fail.

The "Close" button calls window.close() which only works if the window was opened programmatically (via window.open). If the user navigated directly to this page, the button will have no effect. Consider adding a fallback redirect.

🔧 Suggested fallback
 <Button
   variant="primary/medium"
-  onClick={() => window.close()}
+  onClick={() => {
+    window.close();
+    // Fallback if window.close() doesn't work
+    setTimeout(() => {
+      window.location.href = "/";
+    }, 100);
+  }}
   className="w-full"
 >
   Close
 </Button>
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (2)

243-246: Magic number in setTimeout delay.

The 100ms delay before redirect seems arbitrary. Consider either removing it if unnecessary, or adding a comment explaining why it's needed (e.g., to ensure state updates complete).

📝 Add explanatory comment
     hasTriggeredMarketplaceRedirectRef.current = true;
+    // Small delay to ensure state updates complete before navigation
     setTimeout(() => {
       window.location.href = nextUrl;
     }, 100);

420-434: Unused dependency in handleSkipOnboarding.

nextUrl is in the dependency array but is not used in the callback body. This suggests either dead code or a missing feature.

🧹 Remove unused dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (1)

937-939: Import at end of file is unconventional.

Moving the import to the top of the file with other imports would follow standard conventions and improve readability.

📝 Move import to top of file

Move line 937 to the imports section at the top of the file (after line 62):

import { VercelOnboardingModal } from "~/components/integrations/VercelOnboardingModal";
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13489c6 and 28f5e4e.

📒 Files selected for processing (19)
  • .changeset/vercel-integration.md
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/auth.github.callback.tsx
  • apps/webapp/app/routes/auth.google.callback.tsx
  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • packages/core/src/v3/schemas/api.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/vercel-integration.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/auth.github.callback.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/auth.google.callback.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/core/src/v3/schemas/api.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
🧠 Learnings (29)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `idempotencyKeys.create()` to create idempotency keys for preventing duplicate task executions

Applied to files:

  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
📚 Learning: 2026-02-03T18:26:53.718Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The webapp at apps/webapp is a Remix 2.1 application using Node.js v20

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-04-17T10:27:25.337Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 1923
File: packages/core/src/v3/schemas/api.ts:813-827
Timestamp: 2025-04-17T10:27:25.337Z
Learning: Creating secret environment variables is restricted to the dashboard UI only, and not allowed via the API/SDK for now. The `EnvironmentVariableWithSecret` type in the API schema is for reading/displaying purposes only.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-08-14T12:13:20.455Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.455Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (like the Edit button with PencilSquareIcon) intentionally have no text labels - only icons are shown in the TableCellMenu. This is a deliberate UI design pattern for compact icon-only menu items.

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2025-06-06T23:55:01.933Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/.env.example:4-7
Timestamp: 2025-06-06T23:55:01.933Z
Learning: In the trigger.dev project, .env.example files should contain actual example secret values rather than placeholders, as these help users understand the expected format. The files include clear warnings about not using these defaults in production and instructions for generating proper secrets.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
🧬 Code graph analysis (9)
apps/webapp/app/routes/vercel.onboarding.tsx (4)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/models/message.server.ts (1)
  • redirectWithErrorMessage (201-218)
apps/webapp/app/utils/pathBuilder.ts (2)
  • confirmBasicDetailsPath (75-77)
  • newProjectPath (137-141)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • generateVercelOAuthState (15-23)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)
apps/webapp/app/v3/environmentVariables/repository.ts (8)
  • CreateEnvironmentVariables (21-28)
  • CreateEnvironmentVariables (30-30)
  • CreateResult (32-40)
  • EditEnvironmentVariable (42-52)
  • EditEnvironmentVariable (53-53)
  • Result (75-82)
  • EditEnvironmentVariableValue (67-72)
  • EditEnvironmentVariableValue (73-73)
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (6)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)
  • VercelOnboardingModal (939-939)
  • vercelResourcePath (415-421)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (6)
  • getAvailableEnvSlugs (96-105)
  • getAvailableEnvSlugsForBuildSettings (107-112)
  • EnvSlug (4-4)
  • SyncEnvVarsMapping (45-45)
  • shouldSyncEnvVarForAnyEnvironment (164-179)
  • ALL_ENV_SLUGS (6-6)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelAppInstallPath (162-164)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • VercelCustomEnvironment (77-85)
apps/webapp/app/components/primitives/Paragraph.tsx (1)
  • Paragraph (88-107)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx (1)
  • GitHubAppInstallation (65-71)
apps/webapp/app/routes/magic.tsx (1)
apps/webapp/app/services/referralSource.server.ts (1)
  • trackAndClearReferralSource (35-52)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (6)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (3)
  • loader (140-204)
  • vercelResourcePath (415-421)
  • VercelOnboardingModal (939-939)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)
  • loader (49-116)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (1)
  • loader (38-89)
apps/webapp/app/hooks/useProject.tsx (1)
  • useProject (20-24)
apps/webapp/app/hooks/useEnvironment.tsx (1)
  • useEnvironment (19-23)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelResourcePath (170-178)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (1)
apps/webapp/app/utils/pathBuilder.ts (4)
  • EnvironmentParamSchema (26-28)
  • v3ProjectSettingsPath (476-482)
  • vercelResourcePath (170-178)
  • vercelAppInstallPath (162-164)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (3)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (110-119)
apps/webapp/app/services/vercelIntegration.server.ts (1)
  • VercelIntegrationService (38-599)
apps/webapp/app/services/referralSource.server.ts (1)
apps/webapp/app/services/telemetry.server.ts (1)
  • telemetry (239-245)
apps/webapp/app/models/vercelIntegration.server.ts (2)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (2)
  • projectId (644-684)
  • EnvironmentVariablesRepository (48-864)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (5)
  • envTypeToVercelTarget (80-94)
  • TriggerEnvironmentType (35-35)
  • TriggerEnvironmentType (36-36)
  • SyncEnvVarsMapping (45-45)
  • shouldSyncEnvVar (151-162)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (51)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (6)

9-9: LGTM!

The method signatures are now cleaner with structured option types derived from Zod schemas. This aligns with the TypeScript guidelines for using types and Zod for validation.

Also applies to: 13-14, 51-51


159-177: LGTM!

The inheritance logic correctly propagates isSecret: true from parent variables while allowing non-secret status to fall through to the database default. This aligns with the established behavior that variables default to isSecret: false when not explicitly marked as secrets. Based on learnings: "environment variables should default to isSecret: false when not explicitly marked as secrets in the syncEnvVars functionality."


183-200: LGTM!

The idempotent write check is well-designed. It correctly avoids unnecessary database operations when the secret value and metadata are unchanged, while still ensuring updates occur when isSecret status changes or values differ.


214-243: LGTM!

The branched upsert logic with version tracking is well-implemented. The pattern of using undefined for updates (preserve existing) vs Prisma.JsonNull for creates (explicit null) is consistent and correct. The version incrementing provides good audit trail support for the Vercel integration.


273-273: LGTM!

The edit method now properly tracks version changes and updater information. The transaction ensures atomicity between secret store operations and version increments.

Also applies to: 363-376, 394-395


416-416: LGTM!

The editValue method now correctly increments the version and tracks the updater, maintaining consistency with the other mutation methods.

Also applies to: 475-489

apps/webapp/app/routes/login.mfa/route.tsx (2)

29-29: Referral tracking import looks good.


164-170: Referral tracking hook integrates cleanly with the session commits.

apps/webapp/app/services/referralSource.server.ts (3)

1-14: Cookie configuration is sensible and secure.


25-33: Cookie set/clear helpers look good.


35-52: Referral tracking flow is clear and appropriately scoped.

apps/webapp/app/routes/magic.tsx (2)

9-9: Import is appropriate for the new referral tracking hook.


57-57: Referral tracking call placement is correct.

apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (1)

25-132: Auth, membership verification, and CORS handling look solid.

The loader enforces PAT auth, validates params with Zod, checks org membership, and wraps responses with CORS appropriately.

apps/webapp/app/services/vercelIntegration.server.ts (6)

45-125: Integration lookup + parse flow is clear and safe.

Error handling and schema parsing are consistent and return null on invalid data.


127-239: Project selection and sync flow looks good.

The update/create branching plus API‑key sync and logging are well structured.


241-356: Config and env‑var mapping updates are cleanly handled.

The update pattern keeps integrationData consistent and returns parsed data.


385-479: Onboarding flow is robust with sensible logging and fallbacks.

Pull‑sync and TRIGGER_VERSION propagation are handled defensively.


481-582: Production sync guardrails are sensible.

Early returns on missing env/deployment prevent unnecessary API calls.


584-598: Disconnect flow is straightforward and safe.

Soft‑delete keeps history while removing active linkage.

apps/webapp/app/models/vercelIntegration.server.ts (8)

21-47: Helper normalization and error handling utilities are clean.


137-249: OAuth exchange + token validation flow looks solid.


251-367: Configuration + environment variable fetches are well structured.


369-593: Shared env var retrieval and fallback logic is robust.


595-949: Org integration persistence and API‑key sync flows are clear.


951-1232: Pull‑sync pipeline handles filtering, overrides, and logging well.


1234-1417: Batch upsert + single upsert behavior is consistent with Vercel API constraints.


1419-1502: Project settings and uninstall flows are cleanly guarded.

apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx (1)

24-33: Awaiting the sync call is a good reliability improvement.

apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts (3)

18-71: Type surface for settings/onboarding is clear and well scoped.


73-315: Settings aggregation flow is robust with good guardrails.


320-573: Onboarding data assembly is comprehensive and well‑structured.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (4)

65-105: Loader enhancement for Vercel enablement is clean and minimal.


303-417: Onboarding modal state + fetcher orchestration looks solid.


547-560: Settings panel wiring is straightforward and well‑gated.


615-635: Onboarding modal integration is neatly hooked up.

apps/webapp/app/routes/vercel.onboarding.tsx (5)

1-24: Imports look good.

The imports are well-organized and follow project conventions. Using Zod for validation and typedjson for typed responses aligns with coding guidelines.


25-53: Well-structured Zod schemas using discriminated union pattern.

The schema design correctly uses z.discriminatedUnion for type-safe action handling. The validation is thorough.


55-164: Loader implementation is solid.

Good patterns:

  • Authentication via requireUserId
  • Zod validation with proper error handling
  • Authorization check via membership filtering
  • Proper redirect handling for edge cases (no orgs, invalid params)

270-275: Loading state reset is properly implemented.

The useEffect correctly resets isInstalling when navigation returns to idle, addressing the previous review concern about stuck loading states.


224-235: Hardcoded "prod" environment slug is intentional for Vercel integration.

The environment lookup uses slug: "prod" consistently across the Vercel integration flow. This is documented in the Vercel settings file with a comment ("Default to production environment") and follows the same pattern in project creation (_app.orgs.$organizationSlug_.projects.new/route.tsx). The design limits Vercel integrations to the production environment by default, which is the intended behavior.

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (3)

1-65: Imports and helper function look good.

The formatVercelTargets helper provides clean target label formatting. Imports are well-organized.


66-78: Clean state machine type definition.

Using a string union type for the state machine is appropriate and follows the coding guideline of preferring types over interfaces.


79-141: Component setup is well-structured.

Props are clearly defined, initial state computation is properly memoized with useCallback, and the marketplace context check is appropriately derived from search params.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (7)

1-72: Imports and type definition look good.

The ConnectedVercelProject type properly uses type instead of interface per coding guidelines. Imports are well-organized.


73-139: Schemas and helper function are well-implemented.

The parseVercelStagingEnvironment helper safely handles JSON parsing with proper error handling. The discriminated union pattern for action routing is appropriate.


140-204: Loader implementation is solid.

Good patterns:

  • Proper authentication and authorization
  • Conditional data fetching for onboarding
  • Error handling distinguishes Response errors from unexpected errors
  • Logging includes useful context

305-315: Good security practice on redirect URL validation.

The https-only check for the next URL parameter prevents open redirect vulnerabilities to non-secure destinations. The try-catch handles malformed URLs gracefully.


411-412: Excellent exhaustiveness check.

Using submission.value satisfies never ensures TypeScript will error if a new action type is added to the schema but not handled in the action function.


762-762: Path now correctly includes environment slug.

The envVarsConfigLink properly includes all path segments including environmentSlug, addressing the previous review feedback.


415-421: Do not apply this refactor — the vercelResourcePath functions in pathBuilder.ts and this file have incompatible signatures. The one in pathBuilder.ts accepts typed objects (OrgForPath, ProjectForPath, EnvironmentForPath) and uses helper functions like organizationParam(), while this function accepts plain strings. They are not duplicates and cannot be interchanged.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx:
- Around line 46-55: The loader is parsing configurationId from route params but
the value comes from the query string; update the loader to read configurationId
from the URL's search params (use the existing url variable and
url.searchParams.get("configurationId")) and pass that value into
SearchParamsSchema.parse (e.g., build an input object merging params with
configurationId from url.searchParams.get) before using it to filter
integrations; keep references to SearchParamsSchema, loader, configurationId and
requireOrganization so the change is local to that function.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 553-559: The GitHub warning text in VercelGitHubWarning and the
corresponding hint (both in this file) incorrectly state that the Vercel
integration "spawns Trigger.dev builds"; update the copy to reflect the correct
scope: Vercel only links deployments and does not trigger builds or provide git
metadata (the GitHub app does that). Locate the VercelGitHubWarning component
and the hint text referenced near the other occurrence and replace phrases like
"spawns Trigger.dev builds" and any mention of providing git metadata with
wording such as "links deployments; builds and git metadata are provided by the
GitHub app" (or equivalent concise copy conveying that separation of
responsibilities).

In `@apps/webapp/app/routes/vercel.connect.tsx`:
- Around line 13-27: Update the VercelConnectSchema and the
createOrFindVercelIntegration signature to accept an optional configurationId:
make configurationId optional in VercelConnectSchema (change z.string() to an
optional string/nullable) and update the createOrFindVercelIntegration parameter
type for configurationId to be optional (e.g., configurationId?: string or
string | null) so dashboard flows that omit configurationId pass validation;
ensure callers (e.g., vercel.callback.ts) pass undefined/null when no
configurationId is present and downstream uses (installationId/integrationData)
can handle null/undefined.

In `@apps/webapp/app/routes/vercel.onboarding.tsx`:
- Around line 26-85: The loader validation fails for the expired path because
LoaderParamsSchema requires code; change code to optional (e.g., code:
z.string().optional().nullable()) in LoaderParamsSchema so the loader can accept
?error=expired without a code, and update the loader usages (in export async
function loader) to treat params.data.code as possibly null/undefined (e.g., use
params.data.code ?? null when returning typedjson) so the "Installation Expired"
UI can render.
🧹 Nitpick comments (1)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (1)

110-120: Consider including triggerProject in the not-connected response for API consistency.

The project is already available at line 108 when no integration exists. Including triggerProject in this response would provide a consistent shape regardless of connection status, which simplifies client-side type handling.

♻️ Suggested change for consistent response shape
   if (!integration) {
     return apiCors(
       request,
       json({
         connected: false,
         vercelProject: null,
         config: null,
         syncEnvVarsMapping: null,
+        triggerProject: {
+          id: project.id,
+          name: project.name,
+          slug: project.slug,
+        },
       })
     );
   }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f5e4e and 29a3434.

📒 Files selected for processing (5)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧠 Learnings (15)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The webapp at apps/webapp is a Remix 2.1 application using Node.js v20

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2026-02-03T18:26:53.718Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧬 Code graph analysis (2)
apps/webapp/app/routes/vercel.connect.tsx (5)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • TokenResponse (60-66)
apps/webapp/app/routes/vercel.callback.ts (1)
  • loader (20-74)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • validateVercelOAuthState (25-40)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3ProjectSettingsPath (476-482)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)
apps/webapp/app/utils/pathBuilder.ts (2)
  • OrganizationParamsSchema (18-20)
  • v3ProjectSettingsPath (476-482)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (2)

32-39: Authentication and authorization properly implemented.

The endpoint now correctly authenticates requests using authenticateApiRequestWithPersonalAccessToken before any data access, and the Prisma query at lines 59-63 verifies organization membership via members: { some: { userId: authenticationResult.userId } }. This properly addresses the previously flagged security concern.


51-86: Clean neverthrow pattern with efficient org membership verification.

The fromPromise wrapper and discriminated union return type (type: "not_found" | "success") provide clear control flow. Embedding the membership check in the Prisma query (lines 59-63) is efficient—single query validates access and retrieves data.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@0ski 0ski force-pushed the oskar/feat-vercel-integration branch from 29a3434 to 2953d4c Compare February 5, 2026 12:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 946-956: The current copy in the Vercel onboarding component
(around Header3, Paragraph, and Callout) incorrectly implies that connecting
GitHub will let Vercel trigger builds and perform build synchronization; update
the text to clarify that installing the GitHub app provides source and git
metadata (source: "trigger_github_app") and that the Vercel integration only
links deployments between platforms (does not trigger builds). Edit the
Paragraph and the Callout text to: state that connecting GitHub lets Trigger.dev
read repo metadata and enable deployment linking, and explicitly note that build
execution remains handled by the GitHub app/Vercel build settings—ensure Header3
stays “Connect GitHub Repository” and keep the info Callout tone but correct the
wording to avoid implying automatic builds are performed by the Vercel
integration.

In `@apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx`:
- Around line 118-124: The SideMenuItem labeled "Integrations" (in
OrganizationSettingsSideMenu, the SideMenuItem with props name="Integrations"
and to={organizationVercelIntegrationPath(organization)}) is misleading because
it links directly to the Vercel integration page; either rename this menu item
to "Vercel Integration" to match the destination, or implement a parent
integrations hub route (/settings/integrations) and update the to prop to that
hub while creating a child route for organizationVercelIntegrationPath; update
the data-action value if you rename to keep telemetry consistent.

In `@apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts`:
- Around line 60-290: The call method is incorrectly declared async and mixes
synchronous Result returns with async ResultAsync flows; change
VercelSettingsPresenter.call to be non-async and return a ResultAsync throughout
(update its signature/return type accordingly), replace all early synchronous
ok({...}) returns with okAsync({...}) (and any err returns with errAsync if
present), and ensure the final flow returns the existing ResultAsync from
ResultAsync.combine() so callers can await presenter.call() directly; update any
related imports/types to match ResultAsync if needed and keep helper functions
(checkOrgIntegration, getVercelProjectIntegration, etc.) unchanged.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx:
- Around line 119-143: The uninstall action currently finds the first Vercel
integration for the org without respecting configurationId, which can delete the
wrong installation; update the action that uses
prisma.organizationIntegration.findFirst (in the action function) to also read
configurationId from the request formData (use ActionSchema or parse
formData.get("configurationId")) and include configurationId in the where clause
when present (i.e., add configurationId: configurationIdValue to the where
filter for service "VERCEL" and organizationId), so marketplace installs are
targeted correctly; ensure any uninstall logic that calls repository methods
passes installationId/configurationId only when defined (omit it otherwise).

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 160-179: The code wraps presenter.call(...) (which returns a
ResultAsync) with fromPromise, then treats the unwrapped value as a Result and
calls isOk(), causing runtime errors; change to await the ResultAsync directly
by doing const resultOrFail = await presenter.call({ projectId: project.id,
organizationId: project.organizationId }); then check resultOrFail.isErr() /
resultOrFail.isOk() (or !resultOrFail.isOk()) and update the logger to use
resultOrFail.error when present; remove the fromPromise import/usage and ensure
subsequent uses (resultOrFail.value → resultOrFail.value or resultOrFail.unwrap/
.value as appropriate) operate on the actual Result returned from awaiting
VercelSettingsPresenter.call.

In `@apps/webapp/app/routes/vercel.connect.tsx`:
- Around line 164-167: Validate the incoming next query before adding it to
URLSearchParams to prevent open redirects: in the code that constructs params
(where URLSearchParams, vercelOnboarding, origin and next are handled), only
call params.set("next", next) after ensuring next is a safe relative or
same-origin path (e.g., allow strings that start with "/" and do not contain a
scheme/host, or parse with new URL(next, origin) and confirm url.origin ===
origin and url.pathname startsWith("/")). If validation fails, omit the next
param or replace it with a safe fallback so the downstream settings/redirect
logic cannot be used for an open redirect.

In `@apps/webapp/app/routes/vercel.onboarding.tsx`:
- Around line 265-272: The current onboarding handler always sets params.origin
= "marketplace" which breaks dashboard installs that omit configurationId;
change the logic in app/routes/vercel.onboarding.tsx so origin is set to
"marketplace" only when configurationId is present and to "dashboard" (or omit
marketplace) when configurationId is absent. Also update the corresponding
vercel.connect.tsx validation: add a Zod superRefine to enforce that
configurationId must exist for marketplace flows and that installationId (or
configurationId) is only passed into repository methods when configurationId is
defined, omitting that field for dashboard flows. Ensure you reference the
params, configurationId, origin, vercel.connect.tsx validation, and repository
call sites when making the changes.
🧹 Nitpick comments (6)
apps/webapp/app/routes/auth.github.callback.tsx (1)

58-58: Optional: avoid the extra user lookup inside trackAndClearReferralSource.

You already fetched userRecord above; consider extending the helper to accept user metadata (e.g., createdAt) to skip a second DB query on every successful login.

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

214-243: Simplify redundant ternary expressions.

The ternary options.lastUpdatedBy ? options.lastUpdatedBy : undefined is equivalent to just options.lastUpdatedBy since undefined is returned when the value is falsy anyway. This pattern appears in the update path here and in the edit/editValue methods.

Note: The Prisma.JsonNull usage in the create path (line 240) is correct and should remain as-is.

♻️ Suggested simplification
               await tx.environmentVariableValue.update({
                 where: {
                   id: existingValueRecord.id,
                 },
                 data: {
                   version: {
                     increment: 1,
                   },
-                  lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined,
+                  lastUpdatedBy: options.lastUpdatedBy,
                   valueReferenceId: secretReference.id,
                   ...(options.isSecret !== undefined
                     ? {
                         isSecret: options.isSecret,
                       }
                     : {}),
                 },
               });
apps/webapp/app/routes/confirm-basic-details.tsx (1)

115-122: Guard next before propagating it across redirects.
If /orgs/new later uses next for navigation, ensure it’s restricted to internal paths to avoid open-redirect risks. Consider a minimal guard here and confirm downstream validation.

🔒 Suggested guard for internal-only next
-      if (vercelParams.next) {
-        params.set("next", vercelParams.next);
-      }
+      if (vercelParams.next?.startsWith("/")) {
+        params.set("next", vercelParams.next);
+      }
apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts (1)

73-82: Optional: rename the map variable for clarity.

Using a more descriptive variable name makes the mapping easier to read.

♻️ Suggested refactor
-        ? deployment.integrationDeployments.map((id) => ({
-            id: id.id,
-            integrationName: id.integrationName,
-            integrationDeploymentId: id.integrationDeploymentId,
-            commitSHA: id.commitSHA,
-            createdAt: id.createdAt,
-          }))
+        ? deployment.integrationDeployments.map((integrationDeployment) => ({
+            id: integrationDeployment.id,
+            integrationName: integrationDeployment.integrationName,
+            integrationDeploymentId: integrationDeployment.integrationDeploymentId,
+            commitSHA: integrationDeployment.commitSHA,
+            createdAt: integrationDeployment.createdAt,
+          }))
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (1)

34-36: Unused import detected.

v3ProjectSettingsPath is imported but not used anywhere in this file. Consider removing it to keep imports clean.

♻️ Suggested fix
   v3ProjectPath,
-  v3ProjectSettingsPath,
 } from "~/utils/pathBuilder";
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)

81-85: Validate integrationData before reading teamId/installationId.

Casting to any hides malformed data and can leak undefined into the UI. A small Zod schema keeps this safe and consistent.

♻️ Suggested refactor
+const OrgVercelIntegrationDataSchema = z.object({
+  teamId: z.string().nullable().optional(),
+  installationId: z.string().nullable().optional(),
+});
 // ...
-const integrationData = vercelIntegration.integrationData as any;
-const teamId = integrationData?.teamId ?? null;
-const installationId = integrationData?.installationId ?? null;
+const parsed = OrgVercelIntegrationDataSchema.safeParse(vercelIntegration.integrationData);
+const teamId = parsed.success ? parsed.data.teamId ?? null : null;
+const installationId = parsed.success ? parsed.data.installationId ?? null : null;

As per coding guidelines: Use zod for validation in packages/core and apps/webapp.

@0ski
Copy link
Collaborator Author

0ski commented Feb 5, 2026

@coderabbitai help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
    • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
    • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@0ski
Copy link
Collaborator Author

0ski commented Feb 5, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx (1)

16-18: ⚠️ Potential issue | 🟡 Minor

Return a proper Response object for 405 status.

Returning a plain object { status: 405, body: "..." } won't set the HTTP status code correctly. Remix expects a Response or a result from json()/redirect() helpers.

🔧 Proposed fix
   // Ensure this is a POST request
   if (request.method.toUpperCase() !== "POST") {
-    return { status: 405, body: "Method Not Allowed" };
+    return new Response("Method Not Allowed", { status: 405 });
   }
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelBuildSettings.tsx`:
- Around line 23-25: The local slugToEnvType function is incomplete and should
be replaced by the canonical envSlugToType from vercelProjectIntegrationSchema;
remove the slugToEnvType definition, import envSlugToType from
vercelProjectIntegrationSchema, and call envSlugToType(slug) wherever
slugToEnvType(slug) was used so all slugs (dev, stg, prod, preview) map
correctly to DEVELOPMENT/STAGING/PRODUCTION/PREVIEW.

In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 228-245: The client-side redirect in the useEffect (involving
hasTriggeredMarketplaceRedirectRef, isOpen, fromMarketplaceContext, nextUrl,
hasProjectSelected, isGitHubConnectedForOnboarding) assigns query-derived
nextUrl directly to window.location.href which can allow unsafe schemes; before
setting window.location.href validate nextUrl by constructing a URL object and
ensure its protocol is either "https:" or it shares the current origin
(same-origin), and reject or ignore redirects with other schemes (e.g.,
"javascript:")—only after passing this guard set
hasTriggeredMarketplaceRedirectRef.current = true and invoke the delayed
redirect.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:
- Around line 190-205: Wrap the Vercel cleanup calls in a try/catch so failures
are best-effort: when using VercelIntegrationService (calls to
getVercelProjectIntegration and removeSyncEnvVarForEnvironment) and the
prisma.runtimeEnvironment lookup, catch any errors from
removeSyncEnvVarForEnvironment (and optionally
getVercelProjectIntegration/runtimeEnvironment) and log them (e.g.,
processLogger.error or console.error) instead of rethrowing, ensuring the
surrounding delete action that uses submission.value.key and
submission.value.environmentId still returns success even if the Vercel sync
cleanup fails.
- Around line 120-125: The schema for the action payload currently defines
syncEnabled as z.string().transform(...) which accepts any string and coerces
non-"true" to false; update the schema definition for syncEnabled in the
z.object used for the "update-vercel-sync" action to first constrain values to
z.union([z.literal("true"), z.literal("false")]) and then apply the transform to
convert the literal "true"/"false" to a boolean, so only "true" or "false"
inputs are valid before coercion.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:
- Around line 342-357: The effect currently treats { onboardingData: null } as
"not loaded" and repeatedly calls vercelFetcher.load; update the conditions in
the useEffect (the block using hasQueryParam, vercelIntegrationEnabled,
vercelFetcher, isModalOpen, openVercelOnboarding) so you only call
vercelFetcher.load when vercelFetcher.data is strictly undefined (i.e., hasn't
been fetched yet) and vercelFetcher.state === "idle", and treat onboardingData
=== null as a valid loaded response (do not re-load); keep the existing behavior
that opens the modal only when vercelFetcher.data?.onboardingData is truthy and
isModalOpen is false.

In `@apps/webapp/app/routes/login._index/route.tsx`:
- Line 170: The GitHub and Google auth link constructions in this route (the to=
values that build `/login/github?...` and `/login/google?...` around the GitHub
and Google auth buttons) do not encode data.redirectTo, causing malformed URLs
when redirectTo contains special chars; update those constructions to wrap
redirectTo with encodeURIComponent (same as used for the magic link) so both
`/login/github?redirectTo=${encodeURIComponent(data.redirectTo)}` and
`/login/google?redirectTo=${encodeURIComponent(data.redirectTo)}` are used
(ensure you handle the conditional absence of data.redirectTo the same way as
the magic link).

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx:
- Around line 391-418: The Cancel button currently wrapped in DialogClose
bypasses the preventDismiss guard; update the modal footer so the Cancel button
does not close when preventDismiss is true by conditionally rendering or
altering it: if preventDismiss is true, render a disabled or non-DialogClose
plain Button (or a Button with an onClick that checks preventDismiss and returns
early) instead of the DialogClose-wrapped Cancel, otherwise keep the
DialogClose-wrapped Cancel; reference the preventDismiss prop, the DialogClose
component, and the setIsModalOpen/isModalOpen state to implement the conditional
behavior.
🧹 Nitpick comments (10)
apps/webapp/app/services/telemetry.server.ts (1)

59-70: Consider including referralSource in the "user created" event.

The referralSource is passed to identify() but not included in the "user created" capture event. If attribution analysis requires correlating referral sources with user creation events, you may want to include it here as well.

💡 Optional enhancement
         this.#capture({
           userId: user.id,
           event: "user created",
           eventProperties: {
             email: user.email,
             name: user.name,
             authenticationMethod: user.authenticationMethod,
             admin: user.admin,
             createdAt: user.createdAt,
+            ...(referralSource && { referralSource }),
           },
         });
apps/webapp/app/services/referralSource.server.ts (1)

49-50: Consider extracting the magic number as a named constant.

The 30-second threshold for determining a "new user" is a domain-specific value that would benefit from a descriptive name.

♻️ Suggested improvement
+const NEW_USER_THRESHOLD_MS = 30 * 1000; // 30 seconds
+
 export async function trackAndClearReferralSource(
   request: Request,
   userId: string,
   headers: Headers
 ): Promise<void> {
   const referralSource = await getReferralSource(request);
   if (!referralSource) return;

   headers.append("Set-Cookie", await clearReferralSourceCookie());

   const user = await prisma.user.findUnique({ where: { id: userId } });
   if (!user) return;

   const userAge = Date.now() - user.createdAt.getTime();
-  if (userAge >= 30 * 1000) return;
+  if (userAge >= NEW_USER_THRESHOLD_MS) return;

   telemetry.user.identify({ user, isNewUser: true, referralSource });
 }
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (1)

34-34: Unused import detected.

v3ProjectSettingsPath is imported but never used in this file. Consider removing it to keep imports clean.

🧹 Remove unused import
 import {
   newProjectPath,
   OrganizationParamsSchema,
   organizationPath,
   selectPlanPath,
   v3ProjectPath,
-  v3ProjectSettingsPath,
 } from "~/utils/pathBuilder";
apps/webapp/app/routes/vercel.configure.tsx (1)

14-44: Consider using safeParse for more informative error responses.

Using parse throws a generic Zod error on invalid input. Using safeParse would allow returning a more descriptive 400 response when configurationId is missing.

🛡️ Optional: Better error handling with safeParse
 export const loader = async ({ request }: LoaderFunctionArgs) => {
   const url = new URL(request.url);
   const searchParams = Object.fromEntries(url.searchParams);
   
-  const { configurationId } = SearchParamsSchema.parse(searchParams);
+  const parsed = SearchParamsSchema.safeParse(searchParams);
+  if (!parsed.success) {
+    throw new Response("Missing or invalid configurationId", { status: 400 });
+  }
+  const { configurationId } = parsed.data;
apps/webapp/app/components/integrations/VercelLogo.tsx (1)

1-12: Consider adding aria-hidden="true" for accessibility.

Since this is a decorative logo that doesn't convey information, adding aria-hidden="true" prevents screen readers from announcing it unnecessarily.

♿ Proposed accessibility improvement
 export function VercelLogo({ className }: { className?: string }) {
   return (
     <svg
       viewBox="0 0 76 65"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
       className={className}
+      aria-hidden="true"
     >
       <path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
     </svg>
   );
 }
apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx (1)

59-62: Type signature includes "DEVELOPMENT" but it's never passed.

The function signature accepts "DEVELOPMENT" as a valid environmentType, but the caller explicitly excludes it. Consider removing it from the union for consistency, or add a guard inside the function.

♻️ Option 1: Narrow the type signature
 async function syncApiKeyToVercel(
   projectId: string,
-  environmentType: "PRODUCTION" | "STAGING" | "PREVIEW" | "DEVELOPMENT",
+  environmentType: "PRODUCTION" | "STAGING" | "PREVIEW",
   apiKey: string
 ): Promise<void> {
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (1)

422-428: Reuse the shared path builder to avoid drift.

Now that apps/webapp/app/utils/pathBuilder.ts exports vercelResourcePath, consider delegating to it here so the route and UI stay in sync.

♻️ Suggested refactor
-import { EnvironmentParamSchema, v3ProjectSettingsPath, vercelAppInstallPath } from "~/utils/pathBuilder";
+import {
+  EnvironmentParamSchema,
+  v3ProjectSettingsPath,
+  vercelAppInstallPath,
+  vercelResourcePath as vercelResourcePathFromBuilder,
+} from "~/utils/pathBuilder";
@@
 export function vercelResourcePath(
   organizationSlug: string,
   projectSlug: string,
   environmentSlug: string
 ) {
-  return `/resources/orgs/${organizationSlug}/projects/${projectSlug}/env/${environmentSlug}/vercel`;
+  return vercelResourcePathFromBuilder(
+    { slug: organizationSlug },
+    { slug: projectSlug },
+    { slug: environmentSlug }
+  );
 }
internal-packages/database/prisma/schema.prisma (1)

2119-2139: Inconsistent indentation in OrganizationProjectIntegration model.

This model uses tabs while the rest of the schema uses spaces. Standardize to spaces for consistency with the rest of the file.

🔧 Suggested fix
 model OrganizationProjectIntegration {
-	id String `@id` `@default`(cuid())
-	
-	organizationIntegration     OrganizationIntegration `@relation`(fields: [organizationIntegrationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
-	organizationIntegrationId   String
-	
-	project     Project `@relation`(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+  id String `@id` `@default`(cuid())
+
+  organizationIntegration   OrganizationIntegration `@relation`(fields: [organizationIntegrationId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+  organizationIntegrationId String
+
+  project   Project `@relation`(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
   projectId   String
-  
+
   externalEntityId  String /// Identifier for webhooks, for example Vercel's projectId
   integrationData   Json /// Save useful data like config or external entity name
   installedBy       String? /// UserId who installed the integration

   createdAt DateTime `@default`(now())
   updatedAt DateTime `@updatedAt`
   deletedAt DateTime?
-  
+
   @@index([projectId])
   @@index([projectId, organizationIntegrationId])
   @@index([externalEntityId])
 }
apps/webapp/app/services/vercelIntegration.server.ts (1)

571-584: Type cast as any for Vercel SDK target array.

The cast target: ["production"] as any works around SDK typing constraints. This is acceptable given SDK limitations, but consider adding a comment explaining why the cast is needed.

🔧 Suggested improvement
         requestBody: {
           key: "TRIGGER_VERSION",
           value: currentDeployment.version,
+          // Cast required: SDK expects a specific union type but string[] is valid at runtime
           target: ["production"] as any,
           type: "encrypted",
         },
apps/webapp/app/models/vercelIntegration.server.ts (1)

376-381: Consider stricter typing for integration configuration response.

The response type includes [key: string]: any which bypasses type safety. Consider defining a more specific type or using Zod for runtime validation.

       return response.json() as Promise<{
         id: string;
         teamId?: string | null;
         projects?: string[];
-        [key: string]: any;
       }>;

@0ski
Copy link
Collaborator Author

0ski commented Feb 5, 2026

@coderabbitai resolve

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Comments resolved.

@0ski 0ski force-pushed the oskar/feat-vercel-integration branch from 71f4625 to 2f03e2a Compare February 6, 2026 13:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 1031-1048: The modal currently assigns the raw nextUrl to
window.location.href in both the "Complete" and "Skip for now" button handlers,
which bypasses the existing redirect guard; update both handlers (the branch
that runs when isGitHubConnectedForOnboarding && fromMarketplaceContext &&
nextUrl and the else branch that checks fromMarketplaceContext && nextUrl) to
call safeRedirectUrl(nextUrl) and only set window.location.href to the returned
value when it is a non-empty/safe URL, keeping setState("completed") behavior
unchanged; ensure you reference safeRedirectUrl, nextUrl, setState, and
window.location.href in the VercelOnboardingModal button click handlers.

In `@apps/webapp/app/routes/login.magic/route.tsx`:
- Around line 63-71: The code stores an unchecked redirectTo query parameter
into the session (via setRedirectTo and commitRedirectSession) and later uses it
in magic.tsx's redirect call, allowing open redirects; fix by
validating/sanitizing redirectTo before storing (or before use) — implement or
call a helper like isSafeRedirect that ensures redirectTo is either a
same-origin relative path (starts with "/" and not "//", no protocol/host) or
matches a whitelist of allowed paths, and if validation fails use "/" as the
default; update the logic around setRedirectTo/commitRedirectSession in
route.tsx to only store the sanitized value (and likewise enforce the same check
in magic.tsx if stored values might be untrusted).

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 279-290: The parsed syncEnvVarsMapping is currently coerced with a
cast and may be malformed; validate it with the existing
SyncEnvVarsMappingSchema instead of blind casting: use
safeJsonParse(syncEnvVarsMapping) to get the parsed value, then call
SyncEnvVarsMappingSchema.safeParse (or parse) to validate and handle errors
(fail early or return a user-friendly error) and pass the validated value
(replace parsedSyncEnvVarsMapping) into vercelService.completeOnboarding; update
any variable name references (parsedSyncEnvVarsMapping) accordingly so only a
zod-validated object is sent.

In `@apps/webapp/app/utils/pathBuilder.ts`:
- Around line 170-178: Remove the unused vercelResourcePath function from
pathBuilder.ts: locate the exported function named vercelResourcePath, delete
its definition and any related export entry so it no longer contributes dead
code or duplication, and run a quick grep for vercelResourcePath to confirm
there are no remaining references; keep the active implementation used by the
settings route and VercelOnboardingModal untouched.
🧹 Nitpick comments (4)
apps/webapp/app/routes/_app.orgs.new/route.tsx (1)

72-91: Consider using Zod to validate the Vercel integration search params.

The logic is correct and the redirect is safely constructed. One small suggestion: the coding guidelines call for Zod-based validation in the webapp. You could parse the search params through a small Zod schema for consistency with the rest of the codebase (e.g., vercel.connect.tsx).

♻️ Optional refactor
+    const VercelRedirectParams = z.object({
+      code: z.string(),
+      configurationId: z.string(),
+      integration: z.literal("vercel"),
+      next: z.string().optional(),
+    });
+
     const url = new URL(request.url);
-    const code = url.searchParams.get("code");
-    const configurationId = url.searchParams.get("configurationId");
-    const integration = url.searchParams.get("integration");
-    const next = url.searchParams.get("next");
-
-    if (code && configurationId && integration === "vercel") {
-      // Redirect to projects/new with params preserved
-      const params = new URLSearchParams({
-        code,
-        configurationId,
-        integration,
-      });
-      if (next) {
-        params.set("next", next);
+    const vercelResult = VercelRedirectParams.safeParse(
+      Object.fromEntries(url.searchParams)
+    );
+
+    if (vercelResult.success) {
+      const { code, configurationId, integration, next } = vercelResult.data;
+      const params = new URLSearchParams({ code, configurationId, integration });
+      if (next) params.set("next", next);
       const redirectUrl = `${organizationPath(organization)}/projects/new?${params.toString()}`;
       return redirect(redirectUrl);
     }

As per coding guidelines, "Use zod for validation in packages/core and apps/webapp".

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (2)

183-200: The skip-if-unchanged check does not account for lastUpdatedBy changes.

If a caller sends the same value and same isSecret but a different lastUpdatedBy (e.g., a Vercel integration re-sync that should update the audit trail), the write will be skipped and lastUpdatedBy won't be updated. This is fine if lastUpdatedBy is intended to mean "who last changed the value," but could be surprising if it means "who last set the value."

Worth confirming this is the intended semantic — if so, a brief comment in code would help future maintainers.


214-243: Consistent create/update pattern with a minor readability nit.

The differentiation between Prisma.JsonNull for creates (Line 240) and undefined for updates (Line 223) is correct — Prisma requires an explicit JSON null for new rows while undefined means "don't touch the field" on updates. The version: { increment: 1 } / version: 1 split is also correct.

The ternary options.lastUpdatedBy ? options.lastUpdatedBy : <fallback> can be simplified to options.lastUpdatedBy ?? <fallback> since the value is either an object (always truthy) or undefined. This applies to all four occurrences (Lines 223, 240, 374, 395, 487).

✏️ Optional: use nullish coalescing for clarity
-                  lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined,
+                  lastUpdatedBy: options.lastUpdatedBy ?? undefined,
-                  lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : Prisma.JsonNull,
+                  lastUpdatedBy: options.lastUpdatedBy ?? Prisma.JsonNull,
packages/core/src/v3/schemas/api.ts (1)

1154-1159: Rename integration to integrationName for clarity and consistency.

The "user" variant uses userId, which is explicit. The "integration" variant should similarly clarify what type of value it holds. The codebase already uses integrationName elsewhere in the same file (line 718) for integration identifiers, and the actual values passed (e.g., "vercel") are integration names, not IDs. This naming aligns with the established pattern.

♻️ Suggested rename for clarity
  source: z
    .discriminatedUnion("type", [
      z.object({ type: z.literal("user"), userId: z.string() }),
-     z.object({ type: z.literal("integration"), integration: z.string() }),
+     z.object({ type: z.literal("integration"), integrationName: z.string() }),
    ])
    .optional(),

0ski added 13 commits February 6, 2026 16:09
Add a full Remix route and UI for managing Vercel integration
settings for a project environment. Implement client and server pieces
including forms, validation schemas, presenters, and service calls so
users can view and update connection details, toggle sync/build/deploy
options, select staging environment, and complete or skip onboarding.

Key changes:
- Add new route file handling Vercel settings and onboarding flows.
- Introduce form schemas (zod) for update, disconnect, complete and skip
  onboarding actions and parsing with conform/to-zod.
- Implement UI components and layout using existing primitives
  (Dialog, Button, Fieldset, Select, Switch, etc.) and helper functions
  (formatVercelTargets, cn).
- Wire server-side logic to look up projects, environments, and org
  integrations and to call VercelIntegrationService and presenters.
- Add types for ConnectedVercelProject, integration data and mappings.
- Add imports for utilities: path builders, message redirects,
  logging, session requirement, and typed fetcher helpers.

Reasoning:
- Provide a unified interface to configure and manage Vercel
  integrations per environment, improving developer experience when
  syncing envvars, triggering builds/deploys, and completing onboarding.
Add routes, loader/action, and path helpers to support Vercel integration
management under organization settings.

- Add organizationIntegrationsPath and
  organizationVercelIntegrationPath helpers to pathBuilder for consistent
  settings URLs.
- Implement a new route
  _app.orgs.$organizationSlug.settings.integrations.vercel.tsx that:
  - validates org params and optional configurationId query param,
  - enforces organization membership and returns 404 if not found,
  - loads the organization's Vercel integration, its team/installation
    IDs, and connected projects,
  - provides action handling scaffolding for uninstall intent and
    integrates with existing services/repositories.
- Include types, UI primitives, and data formatting via remix-typedjson
  to render integration details and manage deletion.

Motivation: expose a dedicated settings page to view and manage Vercel
integrations per organization, enable deep linking to specific
integration configurations, and centralize route/path construction for
consistency across the app.
…r Vercel integration

Add optional referralSource handling across auth flows and telemetry to record
where new users originate. Extend telemetry.user.identify to accept
referralSource and consolidate identify properties into a single object
before sending to PostHog. Invoke this when users sign in via GitHub or
magic link: read referralSource cookie, attach it for users created within
the last 30 seconds (treating them as new users), and clear the cookie
after use.

This improves attribution for signups by capturing and sending referral
information for new users while ensuring the referral cookie is removed
after consumption.
Preserve full search params when redirecting unauthenticated users to
login so Vercel callback params (code, configurationId, etc.) are not
lost.

Add handling for Vercel callbacks that arrive without a state
parameter (common for Vercel-side installations). When state is
absent but configurationId is present:
- Query the user's organizations and projects.
- If the user has no organizations, redirect to basic details
  onboarding with the Vercel params preserved.
- If the user has organizations but no projects, redirect to the new
  project creation page for the first org with the Vercel params.
- If the user has orgs and projects, exchange the code for a token,
  fetch the Vercel integration configuration, find the default project
  and environment, and continue the installation flow (including
  generating a state JWT).

Add imports for new path helpers and OAuth state generator,
and wire in logic to build redirect URLs with next param when present.

Improve error handling and user-facing error redirects for token and
configuration fetch failures.
- replace vercelResourcePath with v3ProjectSettingsPath for linking to
  project settings pages so routing uses the v3 path helper.
- move DialogDescription out of DialogHeader and into DialogContent to
  correct markup/structure and restore the form buttons layout inside the
  dialog content.
- remove unused import (confirmBasicDetailsPath) and unused OAuth state
  generator import to keep callback route imports minimal.
- change secret type from "sensitive"/"plain" to "encrypted" when syncing
  Vercel trigger keys to standardize storage semantics.
- fix a stray/partial line in the Vercel callback file (cleanup after
  parameter parsing).

These changes improve routing consistency, correct dialog markup for
accessibility and layout, standardize secret typing, and clean up unused
imports and stray code.
Introduce utilities to robustly handle Vercel API response shapes:
- add normalizeTarget to coerce target values into string arrays
- add extractEnvs to safely pull envs arrays from union responses

Replace ad-hoc existence checks and inline normalization with these helpers
in getVercelEnvironmentVariables and getVercelEnvironmentVariableValues to
avoid runtime errors when API returns varying shapes (string vs array vs
missing). This simplifies mapping logic and centralizes normalization.

Remove several outdated JSDoc comments and compress inline documentation to
keep the implementation focused and more readable.
- Add getVercelInstallParams(request) to extract Vercel installation
  parameters (code, configurationId, next) from a request URL and only
  return them when the integration param is "vercel" or absent. This
  centralizes install URL parsing for Vercel flows.

- Add requireOrganization(request, organizationSlug) helper that
  ensures the caller is an authenticated member of the organization,
  returning the organization and userId or throwing a 404 response.
  This enforces access control for org-scoped routes.

- Refactor EnvironmentVariablesPresenter to use a Vercel integration
  service for fetching project integration and remove in-presenter
  legacy migration/parsing logic. The presenter now relies on
  VercelIntegrationService.getVercelProjectIntegration(projectId, true)
  to obtain parsed integration data, simplifying responsibilities and
  consolidating Vercel-related parsing/migration in the service layer.

These changes improve separation of concerns, centralize Vercel
integration parsing, and add a reusable org auth helper for request
handlers.
Update Vercel org integration handling to update existing integration
tokens instead of always creating a new record. When an existing org
integration is detected, log the update, call the repository update
method with the new token and metadata, and re-fetch the integration to
use the up-to-date record. This avoids duplicate integrations and keeps
stored tokens current.

Refactor Vercel repo imports and environment lookup to improve error
handling and typing. Return early when getVercelCustomEnvironments fails,
use the typed result (.data) and VercelCustomEnvironment for safer
lookup, and remove several commented section headers and unused helpers
to simplify the module. Also remove an unused presenter import and tidy
up minor formatting.

Why: prevent duplicate org integrations, ensure tokens are refreshed
correctly, and make environment name resolution more robust and typed.
- return uninstall result from uninstallVercelIntegration so caller can
  react when Vercel rejects the request due to invalid credentials.
- detect auth errors more robustly in isVercelAuthError by checking
  statusCode fields on non-Axios error shapes.
- treat 401/403 from Vercel as soft-fail: log a warning and continue to
  clean up local DB records instead of aborting, and surface authInvalid
  flag to the route handler.
- log a warning when uninstall succeeds but the token is invalid; log
  normal info for successful uninstalls.
Add typed structures, schema validation, and token exchange helper
for the Vercel OAuth callback flow. Introduce TokenResponse,
ProjectWithOrganization, StateData, and CallbackParams types to make
intent explicit and reduce implicit any usage.

Add VercelCallbackSchema (zod) to validate incoming callback query
parameters and ensure safer parsing of code/state/error payloads.

Implement exchangeCodeForToken helper that posts to Vercel's token
endpoint, handles errors, logs failures, and returns a normalized token
shape (including raw response). This centralizes HTTP logic and
improves error handling.

Refactor createOrFindVercelIntegration to use the new TokenResponse and
ProjectWithOrganization types, simplify flow comments, and avoid
repetitive code by updating/creating integrations with consistent data.

Overall this change improves type safety, error reporting, and
readability of the Vercel OAuth callback route.
Add a new Remix route apps/webapp/app/routes/onboarding.vercel.tsx
that implements server loader and token exchange to support Vercel
integration onboarding. Introduce schemas (zod) for loader query
parameters and form actions, and add helper exchangeCodeForToken to
call Vercel's OAuth token endpoint with robust error logging.

Fetch the current user's organizations and projects from Prisma in the
loader, validate incoming query params, and handle missing/misconfigured
Vercel client credentials with clear log messages. Add typed TokenResponse
shape, use env variables for client credentials and redirect URI, and
use redirectWithErrorMessage for user-facing failures.

Motivation: enable users to connect a Vercel integration during
onboarding by validating parameters, exchanging OAuth codes, and
presenting organization/project selection backed by the app database.
0ski added 17 commits February 6, 2026 16:09
Change default Vercel integration data to enable atomic builds only
for production and keep pull env vars enabled for all non-dev
environments. This tightens build behavior to match expected defaults
and avoids enabling atomic builds for staging/preview by default.

Add optional integrationDeployments field to deployment schema and
include it in the deployment API response. This introduces a typed
array of integration deployment records (id, integrationName,
integrationDeploymentId, commitSHA, createdAt) so downstream clients
can surface related integration deployment metadata.

Expose Vercel project setting autoAssignCustomDomains in presenter by
fetching it alongside custom environments. Combine fetching of custom
environments and the auto-assign setting into a single helper that
returns both values (or null when unknown), and thread the new
autoAssignCustomDomains flag through the presenter types.

Also rename a UI label from "Pull from Vercel" to "Sync" for clarity.
Adjust column widths and consolidate the "Updated" columns into a
single cell to simplify the table header and row layout. Increase
Key/Value/Environment header widths slightly to better match content
when Vercel integration is enabled. Replace separate columns for
version, updated-by, and updated-at with one "Updated" column that
renders the actor (user avatar/name or Vercel integration) and the
timestamp together.

Also simplify the Actions header sizing to remove a tiny fixed width
and use hiddenLabel with zero width, improving responsiveness.

These changes clean up the table markup, reduce cluttered columns,
and improve alignment and responsiveness of the environment variables
list.
Add an optional discriminated `source` field to the import request schema
to capture whether variables are imported by a user or an integration and to
include the corresponding identifier (userId or integration). Propagate this
source into the database payloads (`lastUpdatedBy`) when creating or
syncing environment variables so the system records who/what initiated the
import.
Add a new component, BuildSettingsFields, to manage Vercel build settings, including options for pulling environment variables before builds, discovering new environment variables, and enabling atomic deployments. Update related routes and schemas to accommodate the new discoverEnvVars functionality, replacing the previous pullNewEnvVars option. This enhances the Vercel integration by providing a more granular control over environment variable management during builds.
Introduce a new VercelOnboardingModal component to streamline the onboarding process for Vercel integration. This modal manages project selection, environment variable synchronization, and build settings configuration. It enhances user experience by providing a structured flow for onboarding, including handling custom environments and GitHub integration. Additionally, refactor related logic in the Vercel integration service and update schemas to support the new onboarding features.
…c and improve error management

- Updated VercelSettingsPresenter to utilize ResultAsync for better error handling and flow control.
- Refactored API key regeneration to sync directly with Vercel, improving reliability.
- Enhanced onboarding and connection routes to handle optional parameters and errors more gracefully.
- Improved JSON parsing with safe handling using Result from neverthrow.
- Updated referral source management to use Zod for type safety.
- General code cleanup and consistency improvements across Vercel-related services and routes.
…ling with improved filtering and error management
Add vercelTeamSlug to OrganizationProjectIntegration data and persist it
when creating or updating Vercel project integrations. Retrieve the team
slug (when available) from the Vercel client and store it in the
integration record so downstream logic can reference the Vercel team
identifier without making extra API calls.

Enhance DeploymentListPresenter to detect presence of a Vercel project
integration and, when available, include a LEFT JOIN to fetch the most
recent integration deployment id for each worker deployment. Parse the
stored integration data using the VercelProjectIntegrationDataSchema and
expose hasVercelIntegration, integrationDeploymentId, and cached Vercel
fields for use when rendering deployment entries.

These changes reduce repeated Vercel API calls and enable linking of
deployments to their Vercel counterparts in the UI.
@0ski 0ski force-pushed the oskar/feat-vercel-integration branch from 2f03e2a to 05d877a Compare February 6, 2026 16:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (1)

108-158: ⚠️ Potential issue | 🟡 Minor

Missing integration === "vercel" guard — inconsistent with org creation route.

In _app.orgs.new/route.tsx (line 79), the Vercel flow is gated on code && configurationId && integration === "vercel", but here (line 123) only code && configurationId is checked. This makes the condition fragile if another integration ever uses similar URL params.

♻️ Suggested fix
+  const integration = url.searchParams.get("integration");
+
   try {
     const project = await createProject({
       ...
     });
 
     // If this is a Vercel integration flow, generate state and redirect to connect
-    if (code && configurationId) {
+    if (code && configurationId && integration === "vercel") {
🤖 Fix all issues with AI agents
In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 223-233: The code is incorrectly using truthy checks that drop
empty-string env values; update the checks to explicitly test for null or
undefined so "" is preserved: in toVercelEnvironmentVariableValue replace "if
(!env.value) return null" with an explicit null/undefined check (e.g., env.value
=== undefined || env.value === null); similarly change the other occurrences
where "if (listValue)" and "if (!getResult.value.value) return null" to explicit
null/undefined checks for listValue and getResult.value.value respectively,
keeping the rest of the function logic intact (references:
toVercelEnvironmentVariableValue, listValue usage, getResult.value.value).

In `@apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts`:
- Around line 233-237: The code currently strips the "dpl_" prefix when building
vercelDeploymentUrl which yields an invalid Vercel link; update the construction
so vercelDeploymentUrl uses deployment.integrationDeploymentId directly (remove
the .replace(/^dpl_/, "") call) while keeping the same null handling and
condition checks (hasVercelIntegration, deployment.integrationDeploymentId,
vercelTeamSlug, vercelProjectName) so the final URL is
`https://vercel.com/${vercelTeamSlug}/${vercelProjectName}/${deployment.integrationDeploymentId}`.

In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug_.projects.new/route.tsx:
- Line 34: Remove the unused import symbol v3ProjectSettingsPath from the
imports in route.tsx; locate the import list that currently includes
v3ProjectSettingsPath and delete that identifier so the file no longer
references an unused symbol and ESLint/type-checking errors are resolved.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.deployments/route.tsx:
- Around line 314-335: The Vercel link is icon-only and needs an accessible
name; update the anchor rendered inside SimpleTooltip (the element using
deployment.vercelDeploymentUrl and VercelLogo) to include an appropriate
aria-label (e.g., "View on Vercel") so screen readers announce its purpose; keep
the existing onClick={(e) => e.stopPropagation()} and target/rel attributes
unchanged, and ensure the aria-label text matches the SimpleTooltip content for
consistency.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:
- Around line 122-126: The schema uses z.enum for environmentType inside the
z.object for the "update-vercel-sync" action; replace
z.enum(["PRODUCTION","STAGING","PREVIEW","DEVELOPMENT"]) with a string-literal
union like z.union([z.literal("PRODUCTION"), z.literal("STAGING"),
z.literal("PREVIEW"), z.literal("DEVELOPMENT")]) so the schema follows the
project's preference for string unions over enums (leave the surrounding
z.object, action literal, key, and syncEnabled transform as-is).
🧹 Nitpick comments (10)
apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts (1)

73-82: Confusing map parameter name id — consider renaming.

The callback parameter id represents a full IntegrationDeployment record, not an identifier. This makes expressions like id.id unnecessarily confusing. A more descriptive name (e.g., integrationDeployment or intDep) would improve readability.

✏️ Suggested rename
     integrationDeployments:
       deployment.integrationDeployments.length > 0
-        ? deployment.integrationDeployments.map((id) => ({
-            id: id.id,
-            integrationName: id.integrationName,
-            integrationDeploymentId: id.integrationDeploymentId,
-            commitSHA: id.commitSHA,
-            createdAt: id.createdAt,
+        ? deployment.integrationDeployments.map((intDep) => ({
+            id: intDep.id,
+            integrationName: intDep.integrationName,
+            integrationDeploymentId: intDep.integrationDeploymentId,
+            commitSHA: intDep.commitSHA,
+            createdAt: intDep.createdAt,
           }))
         : undefined,
apps/webapp/app/routes/_app.orgs.new/route.tsx (1)

72-91: Validate the Vercel integration parameters with Zod.

The code, configurationId, and integration values are extracted from the URL without validation. Per project coding guidelines, Zod should be used for validation in the webapp. Additionally, consider that the OAuth code is being forwarded through an extra redirect hop (org creation → projects/new → /vercel/connect), which increases the chance of code expiry for slow users.

♻️ Suggested validation
+    const VercelIntegrationParams = z.object({
+      code: z.string(),
+      configurationId: z.string(),
+      integration: z.literal("vercel"),
+      next: z.string().optional(),
+    });
+
     // Preserve Vercel integration params if present
     const url = new URL(request.url);
-    const code = url.searchParams.get("code");
-    const configurationId = url.searchParams.get("configurationId");
-    const integration = url.searchParams.get("integration");
-    const next = url.searchParams.get("next");
-
-    if (code && configurationId && integration === "vercel") {
+    const vercelParams = VercelIntegrationParams.safeParse(Object.fromEntries(url.searchParams));
+
+    if (vercelParams.success) {
+      const { code, configurationId, integration, next } = vercelParams.data;
       // Redirect to projects/new with params preserved
       const params = new URLSearchParams({
         code,
         configurationId,
         integration,
       });
       if (next) {
         params.set("next", next);
       }

As per coding guidelines: "Use zod for validation in packages/core and apps/webapp".

apps/webapp/app/components/integrations/VercelBuildSettings.tsx (1)

155-173: Atomic deployments toggle renders unconditionally regardless of availableEnvSlugs.

If "prod" is not in availableEnvSlugs, this toggle still renders and can be toggled on—yet the other two sections scope their per-env switches to availableEnvSlugs. Consider guarding this section:

Suggested guard
+    {availableEnvSlugs.includes("prod") && (
       {/* Atomic deployments */}
       <div>
         ...
       </div>
+    )}
apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts (1)

109-152: Conditional SQL construction is well-structured, but ensure the lateral join has index support.

The LEFT JOIN LATERAL on IntegrationDeployment filters by deploymentId and integrationName with an ORDER BY createdAt DESC LIMIT 1. If this table grows, the query will benefit from a composite index on (deploymentId, integrationName, createdAt).

#!/bin/bash
# Check existing indexes on IntegrationDeployment table
rg -n "IntegrationDeployment" internal-packages/database/prisma/schema.prisma -A 5 -B 2 | head -40
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (1)

108-146: The endpoint is protected and returns a redundant identifier — consider using slug alone.

This PAT-authenticated endpoint (requires org membership verification) returns both triggerProject.id (the internal database UUID) and triggerProject.slug. Since no consuming code found uses the id field and slug already serves as the public project identifier, either remove id from the response or confirm it's necessary for a specific use case. The risk is low due to authentication requirements, but the redundancy suggests unintended exposure.

apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts (1)

296-583: getOnboardingData is ~280 lines of sequential logic — consider decomposing.

This method handles GitHub installations, token validation, client creation, project discovery, env var fetching/merging, and existing-variable mapping all inline. Extracting helpers (e.g., #fetchAndMergeEnvVars, #buildExistingVariablesRecord) would improve readability and testability.

apps/webapp/app/services/vercelIntegration.server.ts (1)

196-231: Duplicated sync-result extraction logic.

Lines 198–207 and 222–231 contain identical patterns for extracting syncResult from the ResultAsync. Extract a small helper to DRY this up.

♻️ Example helper
function toSyncResult(result: Result<{ errors: string[] }, { message: string }>): { success: boolean; errors: string[] } {
  return result.isOk()
    ? { success: result.value.errors.length === 0, errors: result.value.errors }
    : { success: false, errors: [result.error.message] };
}
apps/webapp/app/routes/vercel.connect.tsx (1)

42-51: Consider omitting installationId when configurationId is undefined.

Per the agreed approach, installationId should only be passed when configurationId is defined, using conditional spread instead of passing undefined directly.

Based on learnings: "pass installationId to repository methods only when configurationId is defined (omit the field otherwise)."

♻️ Proposed change
     await VercelIntegrationRepository.updateVercelOrgIntegrationToken({
       integrationId: orgIntegration.id,
       accessToken: tokenResponse.accessToken,
       tokenType: tokenResponse.tokenType,
       teamId: tokenResponse.teamId ?? null,
       userId: tokenResponse.userId,
-      installationId: configurationId,
+      ...(configurationId ? { installationId: configurationId } : {}),
       raw: tokenResponse.raw
     });

Apply the same pattern to the createVercelOrgIntegration call.

Also applies to: 52-63

apps/webapp/app/routes/vercel.install.tsx (2)

16-16: Prefer a function declaration for consistency.

vercel.connect.tsx uses export async function loader(...) while this file uses export const loader = async (...). The coding guideline prefers function declarations. As per coding guidelines, "Use function declarations instead of default exports."

♻️ Proposed change
-export const loader = async ({ request }: LoaderFunctionArgs) => {
+export async function loader({ request }: LoaderFunctionArgs) {

And close with } instead of }; at the end.


32-53: Remove redundant organization lookup — use project.organizationId instead.

findProjectBySlug already verifies org membership and returns the full project object including organizationId. The separate org query (lines 32–38) is unnecessary and makes an extra database call just to get org.id, which is the same as project.organizationId.

♻️ Simplified flow
-  // Find the organization
-  const org = await $replica.organization.findFirst({
-    where: { slug: org_slug, members: { some: { userId: user.id } }, deletedAt: null },
-    orderBy: { createdAt: "desc" },
-    select: {
-      id: true,
-    },
-  });
-
-  if (!org) {
-    throw redirect("/");
-  }
-
   // Find the project
   const project = await findProjectBySlug(org_slug, project_slug, user.id);
   if (!project) {
     logger.warn("Vercel App installation attempt for non-existent project", {
       org_slug,
       project_slug,
       userId: user.id,
     });
     throw redirect("/");
   }

   const stateToken = await generateVercelOAuthState({
-    organizationId: org.id,
+    organizationId: project.organizationId,
     projectId: project.id,
     environmentSlug,
     organizationSlug: org_slug,
     projectSlug: project_slug,
   });

@0ski

This comment was marked as resolved.

…ved environment variable handling, and pagination support
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 517-528: The useEffect handling completeOnboardingFetcher assigns
completeOnboardingFetcher.data.redirectTo directly to window.location.href
(creating an open redirect risk); update this branch to validate and sanitize
the server-provided redirect using the same safeRedirectUrl utility used
elsewhere before navigating: when "redirectTo" exists and is a string, call
safeRedirectUrl(completeOnboardingFetcher.data.redirectTo) (or the equivalent
project helper) and only set window.location.href to the sanitized result if it
returns a safe value; otherwise fall through to setState("completed") or handle
as a no-op. Ensure the change is applied inside the useEffect that reads
completeOnboardingFetcher.data and references completeOnboardingFetcher.state,
and keep the early return for state === "github-connection".

In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 1412-1417: batchUpsertVercelEnvVars currently calls
client.projects.filterProjectEnvs once and uses extractVercelEnvs, which misses
paginated results; change the logic inside batchUpsertVercelEnvVars to
iterate/paginate through client.projects.filterProjectEnvs (using the same
pagination approach used by getVercelEnvironmentVariables) until all pages are
retrieved, aggregate all pages into a single list, then pass that full list into
extractVercelEnvs so dedupe/create logic sees every existing env var and avoids
duplicate creation.
- Around line 91-94: The current fallback string check on the error variable
(the typeof error === 'string' branch) is too brittle because
error.includes('401') / includes('403') matches any occurrence of those digits;
update this logic to (1) first try to read a numeric status from structured
properties if error is an object (check for error.status, error.statusCode, or
error.response?.status), and (2) if you must inspect a string, replace
error.includes checks with a stricter regex that only matches explicit status
patterns (e.g., /\b(?:HTTP[: ]|status[:= ]?)?(401|403)\b/) so only "HTTP 401" /
"status=401" style substrings are accepted; change the branch handling the
identified numbers to return 401/403 accordingly and leave all other cases
untouched.

In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 46-48: Remove the unused migrateIfNeeded parameter from the
getVercelProjectIntegration signature and all call sites: update the function
declaration async getVercelProjectIntegration(projectId: string) to drop
migrateIfNeeded, remove the default value, and update callers (e.g., the call
from EnvironmentVariablesPresenter that currently passes true) to invoke
getVercelProjectIntegration(projectId) with no second argument; ensure
TypeScript signatures/imports are updated and run a build/tsc to catch any
remaining call sites to fix.
🧹 Nitpick comments (9)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)

190-200: Skip logic doesn't account for lastUpdatedBy changes.

When canSkip is true (value and isSecret are unchanged), the loop continues and the lastUpdatedBy field is never updated. If a different actor (e.g., integration vs. user) syncs the same unchanged value, the audit trail will remain stale, pointing to the original updater.

If accurate attribution matters, consider also comparing lastUpdatedBy:

Proposed fix
             const canSkip =
               existingSecret &&
               existingSecret.secret === variable.value &&
               existingValueRecord &&
               (options.isSecret === undefined ||
-                existingValueRecord.isSecret === options.isSecret);
+                existingValueRecord.isSecret === options.isSecret) &&
+              (!options.lastUpdatedBy ||
+                JSON.stringify(existingValueRecord.lastUpdatedBy) ===
+                  JSON.stringify(options.lastUpdatedBy));
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (3)

122-122: Unused destructured variable CompleteOnboardingForm.

CompleteOnboardingForm is destructured from completeOnboardingFetcher but never used anywhere in the component's JSX or handlers. All submissions go through completeOnboardingFetcher.submit(...) directly.

🧹 Remove unused variable
   const completeOnboardingFetcher = useFetcher();
-  const { Form: CompleteOnboardingForm } = completeOnboardingFetcher;

440-453: Unnecessary nextUrl in dependency array.

handleSkipOnboarding references nextUrl in its dependency array but never reads it in the function body. This is harmless but misleading to future readers.

🧹 Remove stale dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);

93-117: Consider decomposing this 1000+ line component into step sub-components.

The modal manages ~15 useEffect hooks, 3 fetchers, and renders 5 distinct step UIs inline. Extracting each step (project selection, env mapping, env var sync, build settings, GitHub connection) into its own component would reduce cognitive load, make each step independently testable, and simplify the state machine transitions.

This isn't urgent for a draft, but will pay off quickly as the flow evolves.

apps/webapp/test/vercelUrls.test.ts (2)

1-2: Test file should live beside the source file.

Per coding guidelines, test files should live beside the files under test. This test should be at apps/webapp/app/v3/vercel/vercelUrls.server.test.ts rather than apps/webapp/test/vercelUrls.test.ts.


29-38: Consider adding a case-sensitivity test.

The regex uses the i flag, so https://VERCEL.COM/dashboard should pass. Adding an explicit test case would document this intent and guard against regressions if the regex is ever edited.

apps/webapp/app/models/vercelIntegration.server.ts (3)

1529-1579: upsertVercelEnvVar re-fetches all env vars for a single upsert.

This private method fetches the full env var list (line 1540) just to find one matching key+target. When called from syncSingleApiKeyToVercel this is a single call, so it's acceptable now. But if it's ever called in a loop, it becomes an N+1 problem. Consider documenting this constraint or refactoring to accept pre-fetched env vars.


241-241: Repository uses a class with all static methods — consider using a plain module (namespace) or standalone functions.

VercelIntegrationRepository is a class with only static methods and no instance state. In TypeScript, a module of exported functions or a plain object is more idiomatic and avoids the this ambiguity with static methods (e.g., this.getVercelClient in line 334 depends on call-site binding).


1586-1596: Refactor to use a proper Vercel SDK method for reading project settings.

The current approach uses updateProject with an empty body as a workaround to read project settings, which is fragile. The Vercel SDK provides better alternatives: use getProjects() to list and filter by ID/name, or call the REST API directly with GET /v9/projects/{idOrName}. Either approach is more maintainable and correctly expresses the intent to read without modifying.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05d877a and 3597818.

📒 Files selected for processing (9)
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
  • apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/vercel.configure.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/vercel.configure.tsx
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • apps/webapp/test/vercelUrls.test.ts
apps/webapp/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Test files should only import classes and functions from app/**/*.ts files and should not import env.server.ts directly or indirectly; pass configuration through options instead

For testable code in the webapp, never import env.server.ts in test files - pass configuration as options instead

Files:

  • apps/webapp/test/vercelUrls.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for running unit tests

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • apps/webapp/test/vercelUrls.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • apps/webapp/test/vercelUrls.test.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
🧠 Learnings (31)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/vercel/vercelUrls.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.test.{ts,tsx} : Test files should only import classes and functions from `app/**/*.ts` files and should not import `env.server.ts` directly or indirectly; pass configuration through options instead

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest for running unit tests

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.test.{ts,tsx} : For testable code in the webapp, never import env.server.ts in test files - pass configuration as options instead

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Test files should live beside the files under test and use descriptive `describe` and `it` blocks

Applied to files:

  • apps/webapp/test/vercelUrls.test.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-06-06T23:55:01.933Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/.env.example:4-7
Timestamp: 2025-06-06T23:55:01.933Z
Learning: In the trigger.dev project, .env.example files should contain actual example secret values rather than placeholders, as these help users understand the expected format. The files include clear warnings about not using these defaults in production and instructions for generating proper secrets.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-08-14T18:35:44.370Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: apps/webapp/app/env.server.ts:764-765
Timestamp: 2025-08-14T18:35:44.370Z
Learning: The BoolEnv helper in apps/webapp/app/utils/boolEnv.ts uses z.preprocess with inconsistent default value types across the codebase - some usages pass boolean defaults (correct) while others pass string defaults (incorrect), leading to type confusion. The helper should enforce boolean-only defaults or have clearer documentation.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Every Trigger.dev task must be exported and have a unique `id` property with no timeouts in the run function

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2024-10-16T12:24:37.279Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 1407
File: apps/kubernetes-provider/src/index.ts:37-39
Timestamp: 2024-10-16T12:24:37.279Z
Learning: In Node.js, when assigning default values to environment variables using the `||` operator (e.g., `const VAR = process.env.VAR || 'default'`), empty strings are treated as falsy values, so the default value will be used without additional checks.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-08-14T10:09:02.528Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: internal-packages/run-engine/src/engine/index.ts:466-467
Timestamp: 2025-08-14T10:09:02.528Z
Learning: In the triggerdotdev/trigger.dev codebase, it's acceptable to pass `string | undefined` types directly to Prisma operations (both create and update). The codebase consistently uses this pattern and the team is comfortable with how Prisma handles undefined values.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
🧬 Code graph analysis (4)
apps/webapp/test/vercelUrls.test.ts (1)
apps/webapp/app/v3/vercel/vercelUrls.server.ts (1)
  • sanitizeVercelNextUrl (5-26)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)
apps/webapp/app/v3/environmentVariables/repository.ts (8)
  • CreateEnvironmentVariables (21-28)
  • CreateEnvironmentVariables (30-30)
  • CreateResult (32-40)
  • EditEnvironmentVariable (42-52)
  • EditEnvironmentVariable (53-53)
  • Result (75-82)
  • EditEnvironmentVariableValue (67-72)
  • EditEnvironmentVariableValue (73-73)
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (8)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)
  • VercelOnboardingModal (936-936)
  • vercelResourcePath (412-418)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (4)
  • getAvailableEnvSlugs (104-113)
  • getAvailableEnvSlugsForBuildSettings (115-120)
  • EnvSlug (5-5)
  • ALL_ENV_SLUGS (7-7)
apps/webapp/app/utils/pathBuilder.ts (2)
  • vercelAppInstallPath (162-164)
  • githubAppInstallPath (156-160)
apps/webapp/app/components/primitives/Dialog.tsx (3)
  • Dialog (117-117)
  • DialogContent (119-119)
  • DialogHeader (120-120)
apps/webapp/app/components/integrations/VercelLogo.tsx (1)
  • VercelLogo (1-12)
apps/webapp/app/components/primitives/FormButtons.tsx (1)
  • FormButtons (3-35)
apps/webapp/app/components/primitives/Tooltip.tsx (4)
  • TooltipProvider (141-141)
  • Tooltip (141-141)
  • TooltipTrigger (141-141)
  • TooltipContent (141-141)
apps/webapp/app/components/integrations/VercelBuildSettings.tsx (1)
  • BuildSettingsFields (23-176)
apps/webapp/app/services/vercelIntegration.server.ts (3)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (9)
  • VercelProjectIntegrationData (61-61)
  • VercelProjectIntegrationDataSchema (51-59)
  • createDefaultVercelIntegrationData (63-82)
  • VercelIntegrationConfig (37-37)
  • SyncEnvVarsMapping (49-49)
  • TriggerEnvironmentType (39-39)
  • TriggerEnvironmentType (40-40)
  • envTypeToSlug (133-144)
  • EnvSlug (5-5)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)
  • projectId (644-684)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • VercelIntegrationRepository (241-1659)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (3)

8-20: LGTM — Type imports align with Zod schemas.

The strongly typed payload imports (CreateEnvironmentVariables, EditEnvironmentVariable, EditEnvironmentVariableValue) correctly use type keyword and correspond to the Zod-inferred types in ./repository.


363-397: LGTM — Edit method versioning and audit trail.

Version increment on update and version: 1 on create are correctly applied. The lastUpdatedBy handling is consistent: preserved on update when unspecified, explicitly set to Prisma.JsonNull on create.


475-489: LGTM — editValue versioning update.

Version increment and lastUpdatedBy handling are consistent with the other methods. The operation is correctly wrapped in the existing transaction.

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (4)

52-65: Well-implemented URL validation helper.

The allowlist is correctly scoped to same-origin and *.vercel.com subdomains, and the regex anchoring prevents bypass via crafted hostnames like evil-vercel.com.


375-401: Env var toggle logic is clean and consistent with opt-out semantics.

The handleToggleEnvVar and handleToggleAllEnvVars callbacks correctly implement an opt-out pattern where an empty mapping means "sync everything" and explicit false entries disable individual vars. The bulk toggle at line 406 (clear mapping = enable all) is consistent.


1032-1077: Redirect safety in GitHub connection buttons looks good.

Both the "Complete" and "Skip for now" paths now correctly validate nextUrl through safeRedirectUrl before assigning to window.location.href.


246-266: Marketplace auto-redirect properly uses safeRedirectUrl.

The auto-redirect effect validates nextUrl before navigation, and the guard ref prevents duplicate redirects.

apps/webapp/app/v3/vercel/vercelUrls.server.ts (1)

1-26: Clean, well-scoped redirect-validation utility.

The regex correctly anchors the hostname, the protocol-relative // check prevents bypass, and wrapping new URL() in try/catch handles malformed input. Implementation looks solid.

apps/webapp/app/services/vercelIntegration.server.ts (4)

405-433: completeOnboarding intentionally preserves existing syncEnvVarsMapping — verify this is the desired behavior.

Line 431 always writes existing.parsedIntegrationData.syncEnvVarsMapping (the old mapping) to the DB, while line 454 passes params.syncEnvVarsMapping (potentially new) to the pull operation. The comment on line 430 explains the intent: avoid overwriting with an empty mapping during onboarding reset.

However, this means any env-var sync selections made during onboarding are used for the initial pull but never persisted. If a user re-enters the settings page later, they'll see the old mapping, not what they selected during onboarding. If this is truly intentional, the comment is sufficient — but this is worth double-checking with the product requirements.

A previous review flagged this same area — noting it here for awareness given the updated comment.


39-44: Good testability pattern: injectable PrismaClient with a default.

The constructor accepting PrismaClient with a default follows the project's recommended testable-service pattern (per realtimeClient.server.ts / realtimeClientGlobal.server.ts convention).


23-37: Uses type instead of interface — compliant with coding guidelines.

All three exported type aliases (VercelProjectIntegrationWithParsedData, VercelProjectIntegrationWithData, VercelProjectIntegrationWithProject) correctly use type as required. As per coding guidelines, **/*.{ts,tsx}: Use types over interfaces for TypeScript.


559-565: Add a clarifying comment explaining the one-time TRIGGER_VERSION sync behavior.

The early return at lines 563–565 prevents TRIGGER_VERSION from being updated if it already exists in Vercel. Since #syncTriggerVersionToVercelProduction is only called during integration setup (not on subsequent deployments), this means the Vercel env var is set once during onboarding and never updated, even when new versions are deployed. A brief comment explaining this intentional one-time-only behavior would help future maintainers.

apps/webapp/app/models/vercelIntegration.server.ts (1)

50-52: No actionable issues found. The isVercelSecretType function correctly excludes "encrypted" by design—encrypted vars from Vercel are intentionally decrypted and synced as non-secret variables to Trigger.dev through a separate code path (lines 506–540), which is the intended behavior.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@apps/webapp/app/routes/_app.orgs`.$organizationSlug_.projects.new/route.tsx:
- Around line 122-137: When createProject succeeds but
runtimeEnvironment.findFirst (prod) returns null, the user is redirected without
context and the newly created project remains in the DB; update the error
handling around the environment lookup in the route (the block using
createProject, runtimeEnvironment.findFirst, and redirectWithErrorMessage) to
either (a) include in the error message that the project was created but not
linked to Vercel (mention project.id or project.name in the message) so the user
isn't confused, or (b) attempt cleanup by deleting the created project (call the
same project delete path in your service/Prisma for the project created by
createProject) before redirecting; implement one of these options in the code
paths handling code && configurationId where environment is falsy.
- Around line 107-111: Validate and sanitize the user-controlled query params
before propagating them: replace raw URLSearchParams.get() uses for code,
configurationId, and next with a zod schema (e.g., create a VercelQuerySchema)
that parses and enforces types and constraints, and specifically validate next
to only allow same-origin paths or entries from an explicit allowlist (or ensure
it is a relative path starting with "/") before attaching it to the redirect to
/vercel/connect; update the code that reads request.url and the variables code,
configurationId, next to use this schema and reject or normalize invalid values
so tainted next values never flow downstream.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 836-840: The current useEffect sets setHasError(true) based solely
on fetcher.state === "idle" && fetcher.data === undefined && hasFetched, which
causes false positives; update the effect that references useEffect,
fetcher.state, fetcher.data, hasFetched, hasError, and setHasError so it only
marks an error when there is an explicit error signal from the fetcher (e.g.,
fetcher.data contains an error property or a non-2xx status) or when the fetcher
response is a thrown Response object (check for Response instance or an
ok/status flag), rather than whenever fetcher.data is undefined during transient
state transitions; in short, add a guard to inspect fetcher.data for concrete
error indicators (or Response type) before calling setHasError(true).
- Around line 244-248: The code is unsafely casting jsonArrayField results to
EnvSlug[] | null (atomicBuilds, pullEnvVarsBeforeBuild, discoverEnvVars) before
calling vercelService.updateVercelIntegrationConfig; replace those casts with a
Zod validation: define a Zod union/schema for EnvSlug and a
z.array(envSlugSchema).nullable(), parse/transform the jsonArrayField outputs
(e.g., atomicBuilds = envArraySchema.parseOrNull(jsonArrayField(...)) or use
safeParse and set null on failure) and pass the validated arrays into
updateVercelIntegrationConfig instead of using "as EnvSlug[] | null"; apply the
same change to the other occurrences (the other block that sets these three
variables).

In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 154-258: selectVercelProject has a race where two concurrent calls
can both see getVercelProjectIntegration === null and call
createVercelProjectIntegration, creating duplicate
OrganizationProjectIntegration rows; fix by performing the read-then-write
inside a DB-level upsert/transaction: use Prisma transaction or a single upsert
on OrganizationProjectIntegration (or add a unique constraint on (projectId,
organizationIntegrationId) and catch unique-violation errors) so only one
integration is created; modify selectVercelProject to attempt an atomic upsert
(or run create inside a serializable transaction that re-reads with FOR UPDATE)
and return the existing row when a unique-constraint error occurs, keeping
subsequent syncApiKeysToVercel and disableAutoAssignCustomDomains logic
unchanged but invoked with the resolved integration.
🧹 Nitpick comments (7)
apps/webapp/app/services/vercelIntegration.server.ts (2)

197-230: Duplicated sync-result handling pattern — extract a helper.

Lines 197–206 and 221–230 contain nearly identical logic for calling syncApiKeysToVercel and normalizing the result. Consider extracting this into a small private method to reduce duplication.

♻️ Suggested extraction
+  `#normalizeSyncResult`(
+    syncResultAsync: Awaited<ReturnType<typeof VercelIntegrationRepository.syncApiKeysToVercel>>
+  ): { success: boolean; errors: string[] } {
+    return syncResultAsync.isOk()
+      ? { success: syncResultAsync.value.errors.length === 0, errors: syncResultAsync.value.errors }
+      : { success: false, errors: [syncResultAsync.error.message] };
+  }

Then replace both occurrences with this.#normalizeSyncResult(syncResultAsync).


567-580: Remove unnecessary as any cast on Vercel SDK type.

The Vercel SDK's createProjectEnv target field expects ('production' | 'preview' | 'development')[]. Since ["production"] already matches this type, the as any cast is unnecessary and masks type safety. Removing it allows TypeScript to catch potential issues if the SDK types change.

♻️ Suggested fix
         requestBody: {
           key: "TRIGGER_VERSION",
           value: currentDeployment.version,
-          target: ["production"] as any,
+          target: ["production"],
           type: "encrypted",
         },
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (1)

343-416: Excessive overlapping useEffects for modal state — high complexity and duplication risk.

There are four separate useEffects (lines 343–361, 365–371, 374–381, 410–416) all managing the same modal open/close state. The second effect (lines 365–371) unconditionally opens the modal whenever hasQueryParam && !isModalOpen, which makes effects #1 and #3 partially redundant — they also open the modal when data is loaded and hasQueryParam is present.

This creates a chain where:

  1. Effect #2 opens modal → sets isModalOpen = true
  2. isModalOpen change triggers effect #1 (it's in its dep array) → it may trigger openVercelOnboarding() again, which is a no-op since modal is already open

While not an infinite loop (since openVercelOnboarding is guarded), the overlapping responsibility is fragile and hard to reason about. Consider consolidating into a single effect or at most two (one for query-param-driven state, one for button-click-driven state).

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (2)

439-452: Unused nextUrl in dependency array of handleSkipOnboarding.

nextUrl is listed in the useCallback dependency array (line 452) but is never referenced inside the callback body. This won't cause bugs but adds unnecessary re-creation of the callback when nextUrl changes.

♻️ Remove unused dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);

92-116: Very large component (~1085 lines) — consider splitting into sub-components per step.

The VercelOnboardingModal component manages ~15 useEffects, 3 fetchers, and renders 5+ distinct UI steps inline. This level of complexity makes it hard to test and maintain. Consider extracting each step (project selection, env mapping, env var sync, build settings, GitHub connection) into focused sub-components and/or extracting the state machine logic into a custom hook.

This was also noted in the PR review objectives as recommendation #10.

Also applies to: 572-577

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)

925-927: Move the import to the top of the file with other imports.

The import { VercelOnboardingModal } at line 925 is separated from other imports at the top of the file. This is unconventional and can confuse readers or linters.

♻️ Move import to top
 // At the top of the file, with other component imports:
+import { VercelOnboardingModal } from "~/components/integrations/VercelOnboardingModal";

 // Remove from bottom:
-import { VercelOnboardingModal } from "~/components/integrations/VercelOnboardingModal";
-
 export { VercelSettingsPanel, VercelOnboardingModal };

578-591: Change-detection with JSON.stringify([...arr].sort()) works but is fragile.

The array comparison logic in useEffect (lines 578–591) creates sorted copies and stringifies for comparison. This works for EnvSlug[] but is sensitive to object references in the dependency array — originalAtomicBuilds etc. are re-created on every render since they're derived from connectedProject.integrationData.config via ?? []. This means the useEffect fires on every render.

Consider memoizing the original values or using a deep comparison utility.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3597818 and 0df16b5.

📒 Files selected for processing (6)
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/utils/pathBuilder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/utils/pathBuilder.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
🧠 Learnings (34)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-14T18:35:44.370Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: apps/webapp/app/env.server.ts:764-765
Timestamp: 2025-08-14T18:35:44.370Z
Learning: The BoolEnv helper in apps/webapp/app/utils/boolEnv.ts uses z.preprocess with inconsistent default value types across the codebase - some usages pass boolean defaults (correct) while others pass string defaults (incorrect), leading to type confusion. The helper should enforce boolean-only defaults or have clearer documentation.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The webapp at apps/webapp is a Remix 2.1 application using Node.js v20

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Keep the `.claude/skills/trigger-dev-tasks/` skill in sync with the latest rules version when SDK features change

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-03T14:34:41.781Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:357-371
Timestamp: 2025-09-03T14:34:41.781Z
Learning: When using Zod's safeParse, the .data property is undefined when parsing fails, but TypeScript may still complain about accessing .data without checking .success first. The suggested approach of checking .success before accessing .data improves type safety and code clarity.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Import from `trigger.dev/core` using subpaths only, never import from root

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
🧬 Code graph analysis (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (6)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)
  • loader (146-201)
  • VercelOnboardingModal (927-927)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)
  • loader (46-113)
apps/webapp/app/routes/vercel.onboarding.tsx (1)
  • loader (56-176)
apps/webapp/app/hooks/useSearchParam.ts (1)
  • useSearchParams (7-64)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelResourcePath (170-176)
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (1)
  • VercelOnboardingModal (92-1085)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (3)
apps/webapp/app/models/message.server.ts (1)
  • redirectWithErrorMessage (201-218)
apps/webapp/app/utils/pathBuilder.ts (1)
  • newProjectPath (137-141)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • generateVercelOAuthState (15-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (2)

35-35: LGTM!

Import is used at line 139 and correctly references the server-side module.


139-157: LGTM — OAuth state construction and redirect look correct.

The JWT-based state with 15-minute expiry is a solid pattern. Hardcoding origin: "marketplace" is appropriate here since the presence of configurationId indicates a marketplace flow. The internal redirect to /vercel/connect correctly bundles all necessary parameters.

apps/webapp/app/services/vercelIntegration.server.ts (3)

39-44: LGTM — Service class with dependency injection.

Clean constructor-based DI for PrismaClient with a sensible default. This follows the project's testable service pattern.


46-82: LGTM — Safe parsing with proper error logging.

getVercelProjectIntegration correctly uses safeParse with error logging and returns null on parse failure rather than throwing. The deletedAt: null filters are properly applied.


598-612: LGTM — Soft-delete for disconnect.

disconnectVercelProject correctly sets deletedAt rather than hard-deleting the row, which is consistent with the deletedAt: null filters used elsewhere.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (2)

613-635: LGTM — Modal rendering with safe defaults.

The VercelOnboardingModal is properly gated by vercelIntegrationEnabled, uses safe defaults (?? false, ?? null), and passes nextUrl through. The safeRedirectUrl guard in the modal handles URL validation.


546-559: Vercel integration settings nesting creates inconsistent access patterns.

The Vercel settings panel (line 546) is nested inside the githubAppEnabled conditional, but the VercelOnboardingModal (line 615) is rendered independently with only vercelIntegrationEnabled. While Vercel functionally requires GitHub to sync environment variables and link deployments (as documented in the VercelGitHubWarning component), this dependency is only enforced for the settings panel, not the onboarding flow.

Either:

  1. Move the settings panel outside the GitHub conditional and add an explicit isGitHubConnected check in the VercelSettingsPanel component (consistent with how VercelGitHubWarning is used), or
  2. Gate the VercelOnboardingModal with the same GitHub check to maintain consistency across the UI
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (4)

51-64: LGTM — safeRedirectUrl properly restricts redirects.

The function correctly limits to same-origin and *.vercel.com over HTTPS, with proper hostname validation via regex. The try/catch handles malformed URLs.


516-530: LGTM — safeRedirectUrl applied to server-provided redirectTo.

The completeOnboardingFetcher.data.redirectTo is now validated through safeRedirectUrl before assignment to window.location.href. This addresses the open-redirect concern from the prior review.


244-265: LGTM — Marketplace auto-redirect uses safeRedirectUrl.

The nextUrl is validated through safeRedirectUrl before being assigned to window.location.href, and the redirect guard ref prevents duplicate redirects.


1034-1065: LGTM — Both "Complete" and "Skip for now" buttons use safeRedirectUrl.

Both button handlers now validate nextUrl through safeRedirectUrl before navigating. This addresses the prior open-redirect concern.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)

146-201: LGTM — Loader with proper auth, validation, and error handling.

The loader correctly requires authentication (requireUserId), validates params with Zod (EnvironmentParamSchema), checks project/environment existence, and handles presenter errors with appropriate HTTP status codes. The fromPromise wrapping issue from the prior review appears resolved.


297-305: ⚠️ Potential issue | 🟠 Major

Server-side redirect allows any HTTPS host — should match client-side restriction.

The server returns redirectTo: next if urlResult.value.protocol === "https:" (line 299), which allows redirects to any HTTPS host. The client-side safeRedirectUrl is more restrictive (same-origin + *.vercel.com). While the client re-validates, defense-in-depth means the server should not return unsafe redirects. Tighten the server check to match.

🔒 Suggested fix
       if (next) {
         const urlResult = Result.fromThrowable(() => new URL(next), (e) => e)();
-        if (urlResult.isOk() && urlResult.value.protocol === "https:") {
+        if (urlResult.isOk() && urlResult.value.protocol === "https:" &&
+            /^([a-z0-9-]+\.)*vercel\.com$/i.test(urlResult.value.hostname)) {
           return json({ success: true, redirectTo: next });
         }
⛔ Skipped due to learnings
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@0ski 0ski force-pushed the oskar/feat-vercel-integration branch from 0df16b5 to 5762f53 Compare February 6, 2026 20:05
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 516-528: The effect handling completeOnboardingFetcher currently
returns early when safeRedirectUrl(completeOnboardingFetcher.data.redirectTo) is
falsy, leaving the modal stuck; update the useEffect in VercelOnboardingModal so
that after detecting "redirectTo" you still call setState("completed") when
safeRedirectUrl returns null (and only perform window.location.href when
validRedirect is truthy), removing the early return path that prevents state
advancement; reference the useEffect, completeOnboardingFetcher,
safeRedirectUrl, setState, and state symbols to locate and change the logic.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 297-302: The server-side check for the `next` redirect is
currently allowing any HTTPS host (via `urlResult.value.protocol === "https:"`);
tighten it to match the client allowlist by only permitting redirects when the
parsed URL's origin equals the incoming request's origin or its hostname ends
with "vercel.com". Update the logic around `urlResult` (the
`Result.fromThrowable(() => new URL(next), ...)` block) to compute the request
origin (e.g., from the incoming `request` or its headers) and then return
success only if `urlResult.isOk()` and ( `urlResult.value.origin ===
requestOrigin` OR `urlResult.value.hostname.endsWith("vercel.com")` ); treat any
other case (including `urlResult.isErr()`) as a disallowed redirect.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0df16b5 and 5762f53.

📒 Files selected for processing (7)
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/utils/pathBuilder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/utils/pathBuilder.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
🧠 Learnings (37)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Import from `trigger.dev/core` using subpaths only, never import from root

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-06T19:53:38.843Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts:233-237
Timestamp: 2026-02-06T19:53:38.843Z
Learning: When constructing Vercel dashboard URLs from deployment IDs, always strip the dpl_ prefix from the ID. Implement this by transforming the ID with .replace(/^dpl_/, "") before concatenating into the URL: https://vercel.com/${teamSlug}/${projectName}/${cleanedDeploymentId}. Consider centralizing this logic in a small helper (e.g., getVercelDeploymentId(id) or a URL builder) and add tests to verify both prefixed and non-prefixed inputs.

Applied to files:

  • apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Keep the `.claude/skills/trigger-dev-tasks/` skill in sync with the latest rules version when SDK features change

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-03T14:34:41.781Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:357-371
Timestamp: 2025-09-03T14:34:41.781Z
Learning: When using Zod's safeParse, the .data property is undefined when parsing fails, but TypeScript may still complain about accessing .data without checking .success first. The suggested approach of checking .success before accessing .data improves type safety and code clarity.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-08-14T18:35:44.370Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: apps/webapp/app/env.server.ts:764-765
Timestamp: 2025-08-14T18:35:44.370Z
Learning: The BoolEnv helper in apps/webapp/app/utils/boolEnv.ts uses z.preprocess with inconsistent default value types across the codebase - some usages pass boolean defaults (correct) while others pass string defaults (incorrect), leading to type confusion. The helper should enforce boolean-only defaults or have clearer documentation.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Database indexes must use CONCURRENTLY to avoid table locks and must be in their own separate migration file

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:48:39.285Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql:14-18
Timestamp: 2026-02-03T18:48:39.285Z
Learning: When adding indexes to existing tables in Prisma migrations, use CONCURRENTLY in a separate migration file to avoid table locks. Indexes on newly created tables (CREATE TABLE) can be created in the same migration file without CONCURRENTLY.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : When editing the Prisma schema, remove extraneous migration lines related to specific tables: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated `TaskRun` indexes

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
🧬 Code graph analysis (4)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)
  • loader (146-201)
  • VercelOnboardingModal (927-927)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelResourcePath (170-176)
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (1)
  • VercelOnboardingModal (92-1085)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (3)
apps/webapp/app/models/message.server.ts (1)
  • redirectWithErrorMessage (201-218)
apps/webapp/app/utils/pathBuilder.ts (1)
  • newProjectPath (137-141)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • generateVercelOAuthState (15-23)
apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts (3)
apps/webapp/app/services/vercelIntegration.server.ts (1)
  • VercelIntegrationService (39-613)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (2)
  • SyncEnvVarsMapping (49-49)
  • EnvSlug (5-5)
apps/webapp/app/v3/environmentVariables/repository.ts (1)
  • EnvironmentVariableUpdater (19-19)
apps/webapp/app/services/vercelIntegration.server.ts (1)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (9)
  • VercelProjectIntegrationData (61-61)
  • VercelProjectIntegrationDataSchema (51-59)
  • createDefaultVercelIntegrationData (63-82)
  • VercelIntegrationConfig (37-37)
  • SyncEnvVarsMapping (49-49)
  • TriggerEnvironmentType (39-39)
  • TriggerEnvironmentType (40-40)
  • envTypeToSlug (133-144)
  • EnvSlug (5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Deno Runtime
🔇 Additional comments (8)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (2)

139-157: OAuth state generation and redirect look correct.

The state JWT is properly constructed with the required fields (organizationId, projectId, environmentSlug, slugs for routing), and origin: "marketplace" is appropriately hardcoded for this code path. The URLSearchParams construction cleanly handles the optional next parameter. The redirect to /vercel/connect correctly delegates the remainder of the OAuth flow.


35-35: Import looks good.

generateVercelOAuthState is correctly imported and used in the Vercel integration branch of the action.

apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts (2)

78-113: Batched updatedByUser lookup is efficient.
Nice use of a de‑duplicated ID set to avoid per‑value queries.


141-213: Vercel integration fields are surfaced cleanly in the payload.
The mapping and defaults read well.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)

341-361: Good guard against repeated onboarding loads.
The vercelFetcher.data === undefined check prevents reload loops.


546-635: Vercel panel + modal wiring looks consistent.
Conditional rendering and data reload hook-up read cleanly.


100-104: No issue found. isVercelSupported is a static boolean property, not a method. The code correctly assigns its boolean value to vercelIntegrationEnabled.

Likely an incorrect or invalid review comment.

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (1)

51-64: Good allowlist in safeRedirectUrl.
Same‑origin + vercel.com is a solid guard.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 190-194: The computed local variable authInvalid (from
onboardingData?.authInvalid || result.authInvalid || false) is redundantly
re-checking result.authInvalid when building the typedjson return; update the
returned object in the block that calls typedjson to use the local authInvalid
variable directly (authInvalid: authInvalid) or simply include authInvalid once,
removing the redundant "|| result.authInvalid" so the return uses the
already-computed value from the authInvalid variable (refer to the authInvalid
const and the typedjson({...}) return).

In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 200-218: The current update spreads ...(parsedData.success ?
parsedData.data : {}) which, when VercelProjectIntegrationDataSchema.safeParse
fails, writes an incomplete integrationData and corrupts future reads; change
the logic in the block that calls VercelProjectIntegrationDataSchema.safeParse
(using variables parsedData and existing.integrationData) to abort the update
when parsedData.success is false—log an error (or throw) and skip/return instead
of performing tx.organizationProjectIntegration.update; alternatively, if you
prefer automatic recovery, merge parsedData.data with explicit defaults for
required fields (e.g., config and syncEnvVarsMapping) before calling
tx.organizationProjectIntegration.update so integrationData always contains
required schema fields.
🧹 Nitpick comments (8)
apps/webapp/app/services/vercelIntegration.server.ts (4)

461-474: Clarify the divergence between syncEnvVarsMapping usage for pull vs. persistence.

Line 461 defines a syncEnvVarsMapping from params (used at line 495 for pullEnvVarsFromVercel), but line 472 deliberately ignores it for persistence. The inline comment helps, but the same variable name used for two different intents is confusing. Consider renaming line 461's variable (e.g., pullSyncMapping) to make the separation explicit.

♻️ Suggested rename for clarity
-    const syncEnvVarsMapping = params.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} };
+    // Used only for the initial pull from Vercel, not persisted as integration data
+    const pullSyncMapping = params.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} };

Then at line 495:

-        syncEnvVarsMapping,
+        syncEnvVarsMapping: pullSyncMapping,

302-348: Read-then-write without transaction in config updates.

updateVercelIntegrationConfig, updateSyncEnvVarsMapping, updateSyncEnvVarForEnvironment, and removeSyncEnvVarForEnvironment all fetch the existing record, mutate in memory, then write back. Concurrent calls can cause lost updates. Since these are settings updates (low contention), the risk is minor, but wrapping in a transaction (or using a JSON merge approach) would be more robust.


524-638: #syncTriggerVersionToVercelProduction is long with many early-return branches.

This private method spans ~115 lines with 6 early-return points and intermingles data fetching, API calls, and error handling. Consider extracting the "resolve Vercel project ID" and "check existing env var" steps into helper methods to improve readability.


609-622: Remove unnecessary as any cast on target field or document the SDK compatibility issue.

The Vercel SDK's createProjectEnv expects target to be an array of 'production' | 'preview' | 'development'. The target: ["production"] as any cast is unnecessary and masks what should be a properly typed value. If a real type mismatch exists with your SDK version, add a comment referencing the specific version or issue so maintainers know when it's safe to remove.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (3)

572-590: Unstable references in useEffect dependency array cause unnecessary re-runs.

originalAtomicBuilds, originalPullEnvVars, originalDiscoverEnvVars, and originalStagingEnv are derived with ?? [] / ?? null on every render. When the source value is nullish, ?? [] creates a new array reference each time, causing the effect to fire on every render even when nothing changed. Wrap with useMemo or move these to a ref.

♻️ Proposed fix using useMemo
+import { useMemo } from "react";

-  const originalAtomicBuilds = connectedProject.integrationData.config.atomicBuilds ?? [];
-  const originalPullEnvVars = connectedProject.integrationData.config.pullEnvVarsBeforeBuild ?? [];
-  const originalDiscoverEnvVars = connectedProject.integrationData.config.discoverEnvVars ?? [];
-  const originalStagingEnv = connectedProject.integrationData.config.vercelStagingEnvironment ?? null;
+  const originalAtomicBuilds = useMemo(
+    () => connectedProject.integrationData.config.atomicBuilds ?? [],
+    [connectedProject.integrationData.config.atomicBuilds]
+  );
+  const originalPullEnvVars = useMemo(
+    () => connectedProject.integrationData.config.pullEnvVarsBeforeBuild ?? [],
+    [connectedProject.integrationData.config.pullEnvVarsBeforeBuild]
+  );
+  const originalDiscoverEnvVars = useMemo(
+    () => connectedProject.integrationData.config.discoverEnvVars ?? [],
+    [connectedProject.integrationData.config.discoverEnvVars]
+  );
+  const originalStagingEnv = useMemo(
+    () => connectedProject.integrationData.config.vercelStagingEnvironment ?? null,
+    [connectedProject.integrationData.config.vercelStagingEnvironment]
+  );

560-560: useActionData() as any loses type safety.

This discards all type information from the action response. Consider typing it against the Conform submission type or using a discriminated union of the possible action responses.


924-926: Import placed at the bottom of the file.

The VercelOnboardingModal import at line 924 is separated from the rest of the imports (lines 1–64). Moving it to the import block at the top improves readability and is more conventional.

apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (1)

149-160: Consolidate duplicate environment type to slug mapping to reduce drift risk.

Both envTypeToSlug (lines 149–160) and envSlug in apps/webapp/app/models/api-key.server.ts (lines 89–103) perform identical mappings. Although the input types differ (TriggerEnvironmentType vs RuntimeEnvironment["type"]), the underlying string values and outputs are identical. Consider exporting envSlug from api-key.server.ts and reusing it in vercelProjectIntegrationSchema.ts, or creating a shared utility to maintain a single source of truth and prevent mapping drift.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5762f53 and 0128f16.

📒 Files selected for processing (3)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧠 Learnings (29)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-14T18:35:44.370Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: apps/webapp/app/env.server.ts:764-765
Timestamp: 2025-08-14T18:35:44.370Z
Learning: The BoolEnv helper in apps/webapp/app/utils/boolEnv.ts uses z.preprocess with inconsistent default value types across the codebase - some usages pass boolean defaults (correct) while others pass string defaults (incorrect), leading to type confusion. The helper should enforce boolean-only defaults or have clearer documentation.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Database indexes must use CONCURRENTLY to avoid table locks and must be in their own separate migration file

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:48:39.285Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql:14-18
Timestamp: 2026-02-03T18:48:39.285Z
Learning: When adding indexes to existing tables in Prisma migrations, use CONCURRENTLY in a separate migration file to avoid table locks. Indexes on newly created tables (CREATE TABLE) can be created in the same migration file without CONCURRENTLY.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : When editing the Prisma schema, remove extraneous migration lines related to specific tables: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated `TaskRun` indexes

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Keep the `.claude/skills/trigger-dev-tasks/` skill in sync with the latest rules version when SDK features change

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-03T14:34:41.781Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:357-371
Timestamp: 2025-09-03T14:34:41.781Z
Learning: When using Zod's safeParse, the .data property is undefined when parsing fails, but TypeScript may still complain about accessing .data without checking .success first. The suggested approach of checking .success before accessing .data improves type safety and code clarity.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧬 Code graph analysis (3)
apps/webapp/app/services/vercelIntegration.server.ts (1)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (9)
  • VercelProjectIntegrationData (77-77)
  • VercelProjectIntegrationDataSchema (67-75)
  • createDefaultVercelIntegrationData (79-98)
  • VercelIntegrationConfig (53-53)
  • SyncEnvVarsMapping (65-65)
  • TriggerEnvironmentType (55-55)
  • TriggerEnvironmentType (56-56)
  • envTypeToSlug (149-160)
  • EnvSlug (5-5)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (2)
apps/coordinator/src/util.ts (1)
  • safeJsonParse (21-31)
apps/webapp/app/models/api-key.server.ts (1)
  • envSlug (89-104)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (8)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (4)
  • envSlugArrayField (31-40)
  • EnvSlug (5-5)
  • getAvailableEnvSlugs (120-129)
  • getAvailableEnvSlugsForBuildSettings (131-136)
apps/webapp/app/presenters/OrganizationsPresenter.server.ts (1)
  • userId (127-184)
apps/webapp/app/utils/pathBuilder.ts (4)
  • EnvironmentParamSchema (26-28)
  • v3ProjectSettingsPath (474-480)
  • vercelAppInstallPath (162-164)
  • vercelResourcePath (170-176)
apps/webapp/app/models/runtimeEnvironment.server.ts (1)
  • findEnvironmentBySlug (116-145)
apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts (1)
  • VercelSettingsPresenter (61-585)
apps/webapp/app/services/vercelIntegration.server.ts (1)
  • VercelIntegrationService (39-655)
apps/webapp/app/v3/vercel/vercelUrls.server.ts (1)
  • sanitizeVercelNextUrl (5-26)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • VercelIntegrationRepository (241-1659)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (10)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (4)

1-12: Clean utility setup with neverthrow.

The safeJsonParse wrapper using Result.fromThrowable and the EnvSlugSchema definition are well-structured. Using Zod's z.enum for EnvSlug is consistent with the project's coding guidelines.


14-40: Good defensive parsing for form field transforms.

Both jsonArrayField and envSlugArrayField gracefully handle invalid input by returning null, and envSlugArrayField filters invalid elements rather than rejecting the whole array — well-documented and appropriate for form input.


42-77: Schemas are well-defined and use Zod correctly.

VercelIntegrationConfigSchema, TriggerEnvironmentType, SyncEnvVarsMappingSchema, and VercelProjectIntegrationDataSchema all use types (not interfaces) and Zod for validation, consistent with the coding guidelines.


188-203: This behavior is intentional and correctly implements a default-allow policy.

The function returns true for missing environment mappings (undefined slugs) as a deliberate design choice: unless an environment setting explicitly disables syncing (value set to false), the variable should be synced. For non-existent environments like "stg" or "preview" when those features aren't enabled, returning true defaults to syncing rather than silently skipping them. This is the correct semantic—the function answers "should we sync this var to any environment?" and the answer is "yes, unless all mapped environments have explicitly disabled it."

apps/webapp/app/services/vercelIntegration.server.ts (2)

39-44: Good: Testable service pattern with injectable PrismaClient.

The constructor follows the recommended pattern from the coding guidelines — accepting a PrismaClient parameter with a default, making it testable without global state. As per coding guidelines: "Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts."


181-253: Good: Serializable transaction addresses the race condition.

The $transaction with isolationLevel: "Serializable" properly prevents duplicate project integrations from concurrent calls. External API calls (syncApiKeysToVercel, disableAutoAssignCustomDomains) are correctly placed outside the transaction to avoid holding locks during network I/O.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (4)

95-116: Good: envSlugArrayField replaces unsafe as EnvSlug[] casts.

The form schemas now use envSlugArrayField for atomicBuilds, pullEnvVarsBeforeBuild, and discoverEnvVars, which validates individual elements against EnvSlugSchema. This properly addresses the previous review concern about unvalidated casts.


147-202: loader is properly authenticated and handles errors well.

requireUserId is called, params are validated with EnvironmentParamSchema.parse, and the presenter result is checked with isErr(). The previous review concern about fromPromise/ResultAsync misuse has been resolved.


296-304: Good: sanitizeVercelNextUrl properly restricts redirect targets.

The previous review concern about an open redirect via the next parameter has been addressed by using sanitizeVercelNextUrl, which restricts to same-origin or vercel.com HTTPS URLs.


835-839: Error detection relies on fetcher.data === undefined after idle.

When the loader throws a Response (e.g., the 500 at line 173), Remix may not populate fetcher.data, so fetcher.data === undefined after idle is the only signal. The hasFetched guard prevents false positives on initial mount, so this approach works for thrown responses. However, a brief race during the loading→idle transition could still flash the error banner momentarily if Remix settles state to "idle" before data is hydrated. In practice this is unlikely with Remix's synchronous state updates, but worth noting.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

…rays and enhance Vercel integration with transaction handling to prevent race conditions
@0ski 0ski force-pushed the oskar/feat-vercel-integration branch from 0128f16 to d23d10d Compare February 6, 2026 20:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 461-497: The persisted syncEnvVarsMapping
(existing.parsedIntegrationData.syncEnvVarsMapping) is intentionally preserved
but the pull call uses the local syncEnvVarsMapping variable derived from
params, causing a mismatch when params.syncEnvVarsMapping is omitted; update the
code so the value passed to VercelIntegrationRepository.pullEnvVarsFromVercel is
the existing parsed mapping when params.syncEnvVarsMapping is null/undefined
(i.e., use params.syncEnvVarsMapping ??
existing.parsedIntegrationData.syncEnvVarsMapping) instead of the current
syncEnvVarsMapping placeholder, ensuring the pull and the persisted
updatedData.syncEnvVarsMapping agree.

In `@apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts`:
- Around line 188-203: The function shouldSyncEnvVarForAnyEnvironment currently
iterates ALL_ENV_SLUGS (including stg/preview) which treats missing-slug entries
as "sync all" even when those environments don't exist; change the signature to
accept a parameter like availableSlugs: string[] (or Set<string>) and iterate
availableSlugs instead of ALL_ENV_SLUGS, keeping the existing logic for checking
mapping[slug] and envSettings[envVarName] !== false so only real/available
environments influence the result; update call sites of
shouldSyncEnvVarForAnyEnvironment to pass the actual environment slugs for the
org.
🧹 Nitpick comments (5)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (3)

7-7: Derive ALL_ENV_SLUGS from the schema to avoid duplication.

EnvSlugSchema.options already provides the array of valid values. Hardcoding a second copy is a DRY violation that can silently drift if a slug is added to the schema but not the array (or vice-versa).

Suggested change
-export const ALL_ENV_SLUGS: EnvSlug[] = ["dev", "stg", "prod", "preview"];
+export const ALL_ENV_SLUGS: readonly EnvSlug[] = EnvSlugSchema.options;

138-225: Three near-identical is*EnabledForEnvironment helpers could share a single implementation.

isDiscoverEnvVarsEnabledForEnvironment, isPullEnvVarsEnabledForEnvironment, and isAtomicBuildsEnabledForEnvironment all follow the exact same pattern: null/empty guard → envTypeToSlugincludes. A single shared helper reduces boilerplate:

Example consolidation
function isFeatureEnabledForEnvironment(
  enabledSlugs: EnvSlug[] | null | undefined,
  environmentType: TriggerEnvironmentType
): boolean {
  if (!enabledSlugs || enabledSlugs.length === 0) return false;
  return enabledSlugs.includes(envTypeToSlug(environmentType));
}

// Then alias or inline:
export const isDiscoverEnvVarsEnabledForEnvironment = isFeatureEnabledForEnvironment;
export const isPullEnvVarsEnabledForEnvironment = isFeatureEnabledForEnvironment;
export const isAtomicBuildsEnabledForEnvironment = isFeatureEnabledForEnvironment;

149-160: envTypeToSlug duplicates envSlug from api-key.server.ts.

Both functions implement identical switch logic to map environment types to slug strings (DEVELOPMENT→"dev", PRODUCTION→"prod", STAGING→"stg", PREVIEW→"preview"). Extract to a shared utility function to maintain consistency and reduce maintenance burden.

apps/webapp/app/services/vercelIntegration.server.ts (2)

302-348: Non-transactional read-then-write on JSON column across multiple update methods.

updateVercelIntegrationConfig, updateSyncEnvVarsMapping, updateSyncEnvVarForEnvironment, and removeSyncEnvVarForEnvironment (lines 302–444) all read the full integrationData JSON, mutate in JS, then write back. Concurrent calls to any of these methods for the same projectId will race on the JSON blob — the last write silently overwrites the other. If these are exclusively UI-driven single-user operations the risk is low, but if any are called from webhooks or background jobs, consider wrapping them in a serializable transaction (as you already do for selectVercelProject).


127-152: Consider adding installedBy to the type annotation.

installedByUserId is optional in the params but maps to installedBy in the Prisma create. The naming difference is minor but could confuse readers. Otherwise, the creation logic with createDefaultVercelIntegrationData is clean.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0128f16 and d23d10d.

📒 Files selected for processing (3)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:27:05.229Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:27:05.229Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-08-14T18:35:44.370Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2390
File: apps/webapp/app/env.server.ts:764-765
Timestamp: 2025-08-14T18:35:44.370Z
Learning: The BoolEnv helper in apps/webapp/app/utils/boolEnv.ts uses z.preprocess with inconsistent default value types across the codebase - some usages pass boolean defaults (correct) while others pass string defaults (incorrect), leading to type confusion. The helper should enforce boolean-only defaults or have clearer documentation.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : Database indexes must use CONCURRENTLY to avoid table locks and must be in their own separate migration file

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-02-03T18:48:39.285Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql:14-18
Timestamp: 2026-02-03T18:48:39.285Z
Learning: When adding indexes to existing tables in Prisma migrations, use CONCURRENTLY in a separate migration file to avoid table locks. Indexes on newly created tables (CREATE TABLE) can be created in the same migration file without CONCURRENTLY.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to internal-packages/database/prisma/migrations/**/*.sql : When editing the Prisma schema, remove extraneous migration lines related to specific tables: `_BackgroundWorkerToBackgroundWorkerFile`, `_BackgroundWorkerToTaskQueue`, `_TaskRunToTaskRunTag`, `_WaitpointRunConnections`, `_completedWaitpoints`, `SecretStore_key_idx`, and unrelated `TaskRun` indexes

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-09-03T14:34:41.781Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:357-371
Timestamp: 2025-09-03T14:34:41.781Z
Learning: When using Zod's safeParse, the .data property is undefined when parsing fails, but TypeScript may still complain about accessing .data without checking .success first. The suggested approach of checking .success before accessing .data improves type safety and code clarity.

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
🧬 Code graph analysis (2)
apps/webapp/app/services/vercelIntegration.server.ts (3)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (8)
  • VercelProjectIntegrationData (77-77)
  • VercelProjectIntegrationDataSchema (67-75)
  • createDefaultVercelIntegrationData (79-98)
  • VercelIntegrationConfig (53-53)
  • TriggerEnvironmentType (55-55)
  • TriggerEnvironmentType (56-56)
  • envTypeToSlug (149-160)
  • EnvSlug (5-5)
apps/webapp/app/models/vercelIntegration.server.ts (2)
  • VercelIntegrationRepository (241-1659)
  • client (499-542)
apps/webapp/app/v3/models/workerDeployment.server.ts (1)
  • findCurrentWorkerDeployment (73-161)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (2)
apps/coordinator/src/util.ts (1)
  • safeJsonParse (21-31)
apps/webapp/app/models/api-key.server.ts (1)
  • envSlug (89-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/webapp/app/services/vercelIntegration.server.ts (6)

1-44: Well-structured service foundation.

Clean separation of concerns with injectable PrismaClient, proper use of type over interface, Zod-based validation imports, and no direct process.env access. The testable service pattern with constructor injection is consistent with the codebase conventions.


46-82: LGTM — safe parsing with proper soft-delete filtering.

Good defensive pattern: logs the parse error with the integration ID for debugging, then returns null rather than propagating corrupted data.


181-253: Good: serializable transaction prevents duplicate project integrations.

The $transaction with isolationLevel: "Serializable" properly addresses the read-then-write race condition for concurrent selectVercelProject calls.


640-654: LGTM — clean soft-delete.

The disconnect correctly soft-deletes via deletedAt and reuses the existing read method that already filters out deleted records.


600-606: This is working as designed. The early return is intentional—TRIGGER_VERSION is meant to be set once during initial Vercel integration setup, not updated on each deployment. Users manage version updates through their deployment pipeline (e.g., CLI → GitHub Actions → Vercel with -e TRIGGER_VERSION=$VERSION), not through the webapp's Vercel integration. The codebase also explicitly excludes TRIGGER_VERSION from general environment variable sync operations, reinforcing that it's managed separately.

Likely an incorrect or invalid review comment.


609-622: No changes needed — upsert: "true" is correct per Vercel SDK specs.

The @vercel/sdk library expects upsert as a query parameter typed as a string (e.g., "true"), not a boolean. The code at line 613 is correct. Similarly, target: ["production"] at line 617 is the correct type (Array<string>); the as any cast is unnecessary but harmless and consistent with other usages in the codebase.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +461 to +497
const syncEnvVarsMapping = params.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} };
const updatedData: VercelProjectIntegrationData = {
...existing.parsedIntegrationData,
config: {
...existing.parsedIntegrationData.config,
pullEnvVarsBeforeBuild: params.pullEnvVarsBeforeBuild ?? null,
atomicBuilds: params.atomicBuilds ?? null,
discoverEnvVars: params.discoverEnvVars ?? null,
vercelStagingEnvironment: params.vercelStagingEnvironment ?? null,
},
//This is intentionally not updated here, in case of resetting the onboarding it should not override the existing mapping with an empty one
syncEnvVarsMapping: existing.parsedIntegrationData.syncEnvVarsMapping,
onboardingCompleted: true,
};

const updated = await this.#prismaClient.organizationProjectIntegration.update({
where: { id: existing.id },
data: {
integrationData: updatedData,
},
});

const orgIntegration = await VercelIntegrationRepository.findVercelOrgIntegrationForProject(
projectId
);

if (orgIntegration) {
const teamId = await VercelIntegrationRepository.getTeamIdFromIntegration(orgIntegration);

const pullResult = await VercelIntegrationRepository.pullEnvVarsFromVercel({
projectId,
vercelProjectId: updatedData.vercelProjectId,
teamId,
vercelStagingEnvironment: params.vercelStagingEnvironment,
syncEnvVarsMapping,
orgIntegration,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent mapping used for pull vs. persist.

Line 472 intentionally preserves the existing syncEnvVarsMapping in the persisted data (per the comment). But line 495 passes the params mapping (or the empty default from line 461) to pullEnvVarsFromVercel. If the user omits syncEnvVarsMapping, the pull runs with an empty mapping while the DB retains the old one — these should probably agree. Consider using the existing mapping for the pull as well when params don't provide one:

🐛 Proposed fix
-    const syncEnvVarsMapping = params.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} };
+    const syncEnvVarsMapping = params.syncEnvVarsMapping ?? existing.parsedIntegrationData.syncEnvVarsMapping ?? { "dev":{}, "stg":{}, "prod":{}, "preview":{} };
🤖 Prompt for AI Agents
In `@apps/webapp/app/services/vercelIntegration.server.ts` around lines 461 - 497,
The persisted syncEnvVarsMapping
(existing.parsedIntegrationData.syncEnvVarsMapping) is intentionally preserved
but the pull call uses the local syncEnvVarsMapping variable derived from
params, causing a mismatch when params.syncEnvVarsMapping is omitted; update the
code so the value passed to VercelIntegrationRepository.pullEnvVarsFromVercel is
the existing parsed mapping when params.syncEnvVarsMapping is null/undefined
(i.e., use params.syncEnvVarsMapping ??
existing.parsedIntegrationData.syncEnvVarsMapping) instead of the current
syncEnvVarsMapping placeholder, ensuring the pull and the persisted
updatedData.syncEnvVarsMapping agree.

Comment on lines +188 to +203
export function shouldSyncEnvVarForAnyEnvironment(
mapping: SyncEnvVarsMapping,
envVarName: string
): boolean {
for (const slug of ALL_ENV_SLUGS) {
const envSettings = mapping[slug];
if (!envSettings) {
return true;
}
if (envSettings[envVarName] !== false) {
return true;
}
}

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

shouldSyncEnvVarForAnyEnvironment ignores whether environments actually exist.

This iterates ALL_ENV_SLUGS including "stg" and "preview" regardless of whether the org has staging/preview environments. If a variable is explicitly disabled for all real environments (dev, prod) but the mapping has no entry for the non-existent "stg" slug, this function returns true because a missing slug means "sync all" — even though that environment doesn't exist.

Consider accepting the available slugs as a parameter:

Suggested change
 export function shouldSyncEnvVarForAnyEnvironment(
   mapping: SyncEnvVarsMapping,
-  envVarName: string
+  envVarName: string,
+  availableEnvSlugs: EnvSlug[] = ALL_ENV_SLUGS
 ): boolean {
-  for (const slug of ALL_ENV_SLUGS) {
+  for (const slug of availableEnvSlugs) {
     const envSettings = mapping[slug];
     if (!envSettings) {
       return true;
     }
     if (envSettings[envVarName] !== false) {
       return true;
     }
   }
 
   return false;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function shouldSyncEnvVarForAnyEnvironment(
mapping: SyncEnvVarsMapping,
envVarName: string
): boolean {
for (const slug of ALL_ENV_SLUGS) {
const envSettings = mapping[slug];
if (!envSettings) {
return true;
}
if (envSettings[envVarName] !== false) {
return true;
}
}
return false;
}
export function shouldSyncEnvVarForAnyEnvironment(
mapping: SyncEnvVarsMapping,
envVarName: string,
availableEnvSlugs: EnvSlug[] = ALL_ENV_SLUGS
): boolean {
for (const slug of availableEnvSlugs) {
const envSettings = mapping[slug];
if (!envSettings) {
return true;
}
if (envSettings[envVarName] !== false) {
return true;
}
}
return false;
}
🤖 Prompt for AI Agents
In `@apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts` around lines 188
- 203, The function shouldSyncEnvVarForAnyEnvironment currently iterates
ALL_ENV_SLUGS (including stg/preview) which treats missing-slug entries as "sync
all" even when those environments don't exist; change the signature to accept a
parameter like availableSlugs: string[] (or Set<string>) and iterate
availableSlugs instead of ALL_ENV_SLUGS, keeping the existing logic for checking
mapping[slug] and envSettings[envVarName] !== false so only real/available
environments influence the result; update call sites of
shouldSyncEnvVarForAnyEnvironment to pass the actual environment slugs for the
org.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant