Provide environment information
Nextjs 14
System:
OS: Linux 6.6 Ubuntu 20.04.6 LTS (Focal Fossa)
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 2.80 GB / 15.58 GB
Container: Yes
Shell: 3.7.1 - /usr/bin/fish
Binaries:
Node: 22.19.0 - /home/c/.local/share/nvm/v22.19.0/bin/node
npm: 10.9.3 - /home/c/.local/share/nvm/v22.19.0/bin/npm
pnpm: 8.6.3 - /home/c/.local/share/pnpm/pnpm
bun: 1.3.14 - /home/c/.bun/bin/bun
Deno: 2.1.4 - /home/c/.deno/bin/deno
Describe the bug
The error says that my token has already been used and I get http status code 422. But I only see one request to api.trigger.dev in the browser network tools, and that one's already getting 422.
Reproduction repo
I hope I don't need to
To reproduce
My code worked before, all I did was update to 4.4.6 - although I cannot be sure if it previous versions were already broken.
Token is generated via Server action.
I click submit, which should submit the trigger task. Note that now there is one call to the trigger.dev API in the network tab, and that is already getting the error TriggerApiError: Cannot trigger decrypt-link with a one-time use token as it has already been used..
Nothing else happening on the server.
The OPTIONS request is there sometimes and sometimes it's not, but it's after the fact anyways
Additional information
For the test I removed the recreate logic, just in case that's leading to some timing issues.
Some files that were used in the screenshots.
useDecryptTask.ts
'use client';
import { useTaskTrigger } from '@trigger.dev/react-hooks';
import type { TriggerOptions } from '@trigger.dev/sdk/v3';
import { useCallback, useEffect, useState } from 'react';
import type { decryptLink } from '../../../trigger/decrypt-link';
import { createTriggerToken } from '../../../utils/trigger';
/**
* Custom hook to manage the decryption task.
*
* Solves the issue of needing to create a trigger token for the decryption task.
* Handles one-time use tokens by clearing after submission.
*
* Avoids Suspense issues by properly handling the promise.
*/
export function useDecryptTask() {
const [triggerToken, setTriggerToken] = useState<string>('placeholder-token');
const [isTokenReady, setIsTokenReady] = useState(false);
const [isTokenLoading, setIsTokenLoading] = useState(false);
// Create a function to fetch token that can be called multiple times
const fetchToken = useCallback(async () => {
if (isTokenLoading) return null;
setIsTokenLoading(true);
try {
const token = await createTriggerToken('decrypt-link');
console.log('Created trigger token:', token.slice(-5));
setTriggerToken(token);
setIsTokenReady(true);
return token;
} catch (err) {
console.error('Failed to create trigger token:', err);
return null;
} finally {
setIsTokenLoading(false);
}
}, [isTokenLoading]);
// Function to clear token after use
const clearToken = useCallback(() => {
console.debug('Clearing trigger token after use');
setTriggerToken('placeholder-token');
setIsTokenReady(false);
}, []);
// Load the token initially - but in an effect, not during render
useEffect(() => {
let isMounted = true;
const loadInitialToken = async () => {
try {
const token = await createTriggerToken('decrypt-link');
console.log('🚀 ~ loadInitialToken ~ token:', token.slice(-5));
if (isMounted) {
setTriggerToken(token);
setIsTokenReady(true);
}
} catch (err) {
console.error('Failed to create initial trigger token:', err);
} finally {
if (isMounted) {
setIsTokenLoading(false);
}
}
};
setIsTokenLoading(true);
loadInitialToken();
return () => {
isMounted = false;
};
}, []);
// Always call the hook with a valid string (even if placeholder)
const taskTrigger = useTaskTrigger<typeof decryptLink>('decrypt-link', {
accessToken: triggerToken,
enabled: isTokenReady,
});
// Wrap submit to also clear the token after use
const submit = useCallback(
async (
params: Parameters<typeof taskTrigger.submit>[0],
options: TriggerOptions
) => {
if (!isTokenReady) {
return Promise.resolve();
}
try {
taskTrigger.submit(params, options);
return await Promise.resolve();
} catch (error) {
// Clear token even on error - it's already been consumed
clearToken();
throw error;
}
},
[taskTrigger, isTokenReady, clearToken]
);
return {
...taskTrigger,
submit,
isTokenReady,
isTokenLoading,
recreateToken: fetchToken,
token: triggerToken, // expose token for debugging
};
}
createTriggerToken.ts
'use server';
import { auth } from '@trigger.dev/sdk';
/**
* Server action to create a trigger token for the "decrypt-link" task.
*
* Quote:
* To authenticate a trigger hook, you must provide a special one-time use “trigger” token.
* These tokens are very similar to Public Access Tokens, but they can only be used once to trigger a task.
* You can generate a trigger token using the auth.createTriggerPublicToken function in your backend code:
*
* @param task The name of the task for which the trigger token is created.
* @returns
*/
export async function createTriggerToken(task: string) {
const performanceStart = performance.now();
const token = await auth.createTriggerPublicToken(task, {
expirationTime: '24hr',
});
const performanceEnd = performance.now();
console.log(
`Created trigger token for task "${task}" in ${
performanceEnd - performanceStart
} ms, token: ${token.slice(-5)}` // last 5 letters of token
);
return token;
}
Some background, what I'm trying to achieve. This is a short running task. I initially used to submit-and-await logic trigger has, that would return it in one go. Now I'm using the handler logic. I create the token on page load, so when the user hits submit, the token is there at least, to make it quicker, because, as I said, this is a short running task so the 300ms to create the token makes a difference.
Also tried to chat with the discord bot, searching the docs, github issues, discussions, google etc.
Provide environment information
Nextjs 14
System:
OS: Linux 6.6 Ubuntu 20.04.6 LTS (Focal Fossa)
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 2.80 GB / 15.58 GB
Container: Yes
Shell: 3.7.1 - /usr/bin/fish
Binaries:
Node: 22.19.0 - /home/c/.local/share/nvm/v22.19.0/bin/node
npm: 10.9.3 - /home/c/.local/share/nvm/v22.19.0/bin/npm
pnpm: 8.6.3 - /home/c/.local/share/pnpm/pnpm
bun: 1.3.14 - /home/c/.bun/bin/bun
Deno: 2.1.4 - /home/c/.deno/bin/deno
Describe the bug
The error says that my token has already been used and I get http status code 422. But I only see one request to
api.trigger.devin the browser network tools, and that one's already getting 422.Reproduction repo
I hope I don't need to
To reproduce
My code worked before, all I did was update to
4.4.6- although I cannot be sure if it previous versions were already broken.Token is generated via Server action.
I click submit, which should submit the trigger task. Note that now there is one call to the trigger.dev API in the network tab, and that is already getting the error
TriggerApiError: Cannot trigger decrypt-link with a one-time use token as it has already been used..Nothing else happening on the server.
The
OPTIONSrequest is there sometimes and sometimes it's not, but it's after the fact anywaysAdditional information
For the test I removed the recreate logic, just in case that's leading to some timing issues.
Some files that were used in the screenshots.
useDecryptTask.ts
createTriggerToken.ts
Some background, what I'm trying to achieve. This is a short running task. I initially used to submit-and-await logic trigger has, that would return it in one go. Now I'm using the handler logic. I create the token on page load, so when the user hits submit, the token is there at least, to make it quicker, because, as I said, this is a short running task so the 300ms to create the token makes a difference.
Also tried to chat with the discord bot, searching the docs, github issues, discussions, google etc.