Skip to content

feat: Allow opting out of TRIGGER_VERSION locking on a per-call basis #3380

@jorgehermo9

Description

@jorgehermo9

Is your feature request related to a problem? Please describe.

have a Next.js app on Vercel that triggers tasks in two Trigger.dev projects from the same Node process:

  • Project A is connected to the Vercel integration. The integration injects TRIGGER_ACCESS_TOKEN and TRIGGER_VERSION into the runtime, and gates Vercel promotion on a matching Trigger.dev deploy. So deploys are atomic between the web app and Project A's tasks.
  • Project B is a separate Trigger.dev project for a codebase that ships on its own schedule. It is not connected to the Vercel integration (that integration is per-project).

I swap the secret key with auth.withAuth({ secretKey }, fn) for calls that target Project B, and that part works fine.

The problem

Every high-level trigger entry point (tasks.trigger(), yourTask.trigger(), batchTrigger(), triggerAndWait(), batchTriggerAndWait()) includes this line when building the request body:

lockToVersion: options?.version ?? getEnvVar("TRIGGER_VERSION")

TRIGGER_VERSION comes from process.env (via std-env). The Vercel integration sets it to Project A's current version, so any call from the same process that targets Project B inherits Project A's version and tries to pin the remote call to a version that does not exist in Project B.

I could not find a supported way to opt out per call:

  • auth.withAuth(config, fn): ApiClientConfiguration has baseURL, secretKey/accessToken, previewBranch, requestOptions, future. No version. The scoped config would be the natural place for "this call is against another project, don't pin it", but the field isn't there.
  • TriggerOptions.version: requires a specific version string. There's no sentinel like "latest" or null. And because of the ?? fallback, passing { version: undefined } quietly reverts to the env var.
  • Mutating process.env.TRIGGER_VERSION: not safe. The SDK reads it synchronously inside trigger_internal between awaits, so a concurrent Project A trigger in the same process could lose its pin. Unsetting it at startup also defeats the whole point of the Vercel integration for Project A calls.

Current workaround

I dropped down to @trigger.dev/core/v3's ApiClient directly, built the request body myself, and left lockToVersion out:

import { ApiClient } from "@trigger.dev/core/v3";

const TRIGGER_API_URL = "https://api.trigger.dev";

export async function triggerRemote(
  taskId: string,
  payload: any,
  options?: { tags?: string[]; ttl?: string }
) {
  const client = new ApiClient(TRIGGER_API_URL, process.env.PROJECT_B_SECRET_KEY!);
  return client.triggerTask(taskId, {
    payload,
    options: {
      payloadType: "application/json",
      tags: options?.tags,
      ttl: options?.ttl,
    },
  });
}

This works. I still get zod-validated request/response shapes and JWT publicAccessToken generation. But I lose most of the SDK's conveniences (idempotency key helpers, parsePayload, stringifyIO, tracing spans, task-context parent-run linking) and I'm now depending on an internal class instead of a public API.

Describe the solution you'd like to see

Proposed API

I'd propose supporting both a scoped override and a per-call override, with the same null sentinel:

Scoped via ApiClientConfiguration (add a version field):

await auth.withAuth({ secretKey: PROJECT_B_KEY, version: null }, async () => {
  await tasks.trigger("some-task", payload); // lockToVersion omitted
});

Good for the cross-project wrapper case, where every call inside the scope should skip version locking.

Per-call via TriggerOptions.version (extend from string to string | null):

await tasks.trigger("some-task", payload, { version: null }); // lockToVersion omitted for this call only

Good for one-off "don't pin this specific call" cases without needing a wrapper.

Both accept the same three states:

  • version: "<string>": explicit pin. Same as today.
  • version: null: explicitly unpinned. Server resolves to the current deployed version. Ignores TRIGGER_VERSION.
  • version: undefined / not set: current behavior (fall back to the env var).

If both are set (scope says null, call passes a string, or vice versa), the per-call value wins.

Describe alternate solutions

Additional information

Why this matters

Cross-project triggering isn't an exotic pattern if you have separate codebases for separate workloads (heavy AI agents, batch pipelines, tenant isolation). And it's more common now that the Vercel integration gives such a nice atomic-deploy story for the "main" project. But the current version-locking behavior makes it awkward to trigger from that atomic runtime into a sibling project. The two supported workarounds are "run a separate Node process" or "drop to the raw ApiClient", and both give up the SDK ergonomics for the common case.

Happy to send a PR if this API shape is in the right direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions