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.
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:
TRIGGER_ACCESS_TOKENandTRIGGER_VERSIONinto 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.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:TRIGGER_VERSIONcomes fromprocess.env(viastd-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):ApiClientConfigurationhasbaseURL,secretKey/accessToken,previewBranch,requestOptions,future. Noversion. 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"ornull. And because of the??fallback, passing{ version: undefined }quietly reverts to the env var.process.env.TRIGGER_VERSION: not safe. The SDK reads it synchronously insidetrigger_internalbetween 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'sApiClientdirectly, built the request body myself, and leftlockToVersionout:This works. I still get zod-validated request/response shapes and JWT
publicAccessTokengeneration. 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
nullsentinel:Scoped via
ApiClientConfiguration(add aversionfield):Good for the cross-project wrapper case, where every call inside the scope should skip version locking.
Per-call via
TriggerOptions.version(extend fromstringtostring | null):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. IgnoresTRIGGER_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.