Skip to content

Fix: Trigger.dev Not Working — Tasks Not Running, Runs Failing, or Dev Server Not Connecting

FixDevs ·

Quick Answer

How to fix Trigger.dev issues — task definition and triggering, dev server setup, scheduled tasks with cron, concurrency and queues, retries, idempotency, and deployment to Trigger.dev Cloud.

The Problem

A task is defined but triggering it does nothing:

import { task } from '@trigger.dev/sdk/v3';

export const myTask = task({
  id: 'my-task',
  run: async (payload: { userId: string }) => {
    console.log('Running for', payload.userId);
  },
});
// trigger() is called but the task never executes

Or the dev server can’t connect:

npx trigger.dev dev
# Error: Could not connect to Trigger.dev

Or a task runs but fails with a timeout:

Run failed: Task execution timed out after 300 seconds

Why This Happens

Trigger.dev v3 runs your task code in a managed runtime (not inside your Next.js server). This architecture has specific requirements:

  • Tasks must be in a trigger/ directory — the trigger.config.ts tells the Trigger.dev CLI where to find tasks. Files outside the configured directory aren’t discovered. Each task file must export a task created with the task() function.
  • trigger.dev dev runs a separate process — the dev server connects to Trigger.dev Cloud (or self-hosted) and executes tasks locally. If the connection fails (wrong API key, network issues), tasks are queued but never picked up.
  • Tasks run in isolated workers — unlike Inngest which calls your HTTP endpoints, Trigger.dev v3 runs your code in its own runtime. This means tasks can’t access your Next.js server’s in-memory state, but they can access databases, APIs, and file systems.
  • Triggering requires the SDK — you call tasks.trigger() or myTask.trigger() from your application code. The trigger sends a message to Trigger.dev, which then executes the task. If the API key or project reference is wrong, the trigger silently fails.

Fix 1: Set Up Trigger.dev v3

npm install @trigger.dev/sdk
npx trigger.dev init
// trigger.config.ts — project configuration
import { defineConfig } from '@trigger.dev/sdk/v3';

export default defineConfig({
  project: 'proj_xxxxxxxxxx',  // From Trigger.dev dashboard
  runtime: 'node',
  logLevel: 'log',
  retries: {
    enabledInDev: true,
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1000,
      maxTimeoutInMs: 10000,
      factor: 2,
    },
  },
  dirs: ['./trigger'],  // Directory containing task files
});
// trigger/example.ts — define a task
import { task, logger } from '@trigger.dev/sdk/v3';

export const processOrder = task({
  id: 'process-order',
  // Max execution time
  maxDuration: 300,  // 5 minutes
  run: async (payload: { orderId: string; userId: string }) => {
    logger.info('Processing order', { orderId: payload.orderId });

    // Step 1: Fetch order
    const order = await db.query.orders.findFirst({
      where: eq(orders.id, payload.orderId),
    });

    if (!order) {
      throw new Error(`Order ${payload.orderId} not found`);
    }

    // Step 2: Process payment
    logger.info('Charging payment', { amount: order.total });
    const payment = await chargePayment(order);

    // Step 3: Send confirmation
    await sendOrderConfirmation(order, payment);

    // Return value is stored with the run
    return { orderId: order.id, paymentId: payment.id, status: 'completed' };
  },
});
# Start the dev server
npx trigger.dev dev

# Or with specific config
TRIGGER_SECRET_KEY=tr_dev_xxx npx trigger.dev dev

Fix 2: Trigger Tasks from Your App

// app/api/orders/route.ts — trigger from an API route
import { tasks } from '@trigger.dev/sdk/v3';
import type { processOrder } from '@/trigger/example';

export async function POST(req: Request) {
  const { orderId, userId } = await req.json();

  // Trigger the task — returns immediately
  const handle = await tasks.trigger<typeof processOrder>('process-order', {
    orderId,
    userId,
  });

  return Response.json({
    message: 'Order processing started',
    runId: handle.id,  // Track the run
  });
}

// Or import the task directly
import { processOrder } from '@/trigger/example';

const handle = await processOrder.trigger({ orderId: '123', userId: '456' });

// Trigger and wait for result
const result = await processOrder.triggerAndWait({ orderId: '123', userId: '456' });
console.log(result.status);  // 'completed'

// Batch trigger — multiple payloads
const handles = await processOrder.batchTrigger([
  { payload: { orderId: '1', userId: '100' } },
  { payload: { orderId: '2', userId: '101' } },
  { payload: { orderId: '3', userId: '102' } },
]);

Fix 3: Scheduled Tasks (Cron)

// trigger/scheduled.ts
import { schedules, logger } from '@trigger.dev/sdk/v3';

// Cron-based scheduled task
export const dailyReport = schedules.task({
  id: 'daily-report',
  cron: '0 9 * * *',  // Every day at 9 AM UTC
  run: async (payload) => {
    logger.info('Generating daily report', {
      timestamp: payload.timestamp,
      lastTimestamp: payload.lastTimestamp,
    });

    const stats = await generateDailyStats();
    await sendReportEmail(stats);

    return { reportGenerated: true, stats };
  },
});

// Cleanup old data weekly
export const weeklyCleanup = schedules.task({
  id: 'weekly-cleanup',
  cron: '0 3 * * 0',  // Every Sunday at 3 AM
  run: async () => {
    const deleted = await db.delete(sessions)
      .where(lt(sessions.expiresAt, new Date()))
      .returning();

    logger.info(`Cleaned up ${deleted.length} expired sessions`);
    return { deletedCount: deleted.length };
  },
});

Fix 4: Concurrency and Queues

// trigger/email.ts — rate-limited task
import { task, queue } from '@trigger.dev/sdk/v3';

// Define a queue with concurrency limits
const emailQueue = queue({
  name: 'email-queue',
  concurrencyLimit: 5,  // Max 5 emails sending at once
});

export const sendEmail = task({
  id: 'send-email',
  queue: emailQueue,
  run: async (payload: { to: string; template: string; data: Record<string, any> }) => {
    await emailProvider.send({
      to: payload.to,
      template: payload.template,
      data: payload.data,
    });
    return { sent: true, to: payload.to };
  },
});

// Per-key concurrency — one task per user at a time
export const syncUserData = task({
  id: 'sync-user-data',
  queue: {
    name: 'user-sync',
    concurrencyLimit: 1,
  },
  run: async (payload: { userId: string }) => {
    // Only one sync runs per user at a time
    await performSync(payload.userId);
  },
});

Fix 5: Retry and Error Handling

// trigger/resilient.ts
import { task, logger, retry } from '@trigger.dev/sdk/v3';

export const callExternalApi = task({
  id: 'call-external-api',
  retry: {
    maxAttempts: 5,
    factor: 2,               // Exponential backoff
    minTimeoutInMs: 1000,    // Start at 1s
    maxTimeoutInMs: 30000,   // Cap at 30s
    randomize: true,         // Add jitter
  },
  run: async (payload: { url: string; data: any }) => {
    const response = await fetch(payload.url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload.data),
    });

    if (response.status === 429) {
      // Rate limited — throw to trigger retry
      const retryAfter = response.headers.get('Retry-After');
      throw new Error(`Rate limited. Retry after ${retryAfter}s`);
    }

    if (response.status >= 500) {
      // Server error — throw to trigger retry
      throw new Error(`Server error: ${response.status}`);
    }

    if (response.status === 400) {
      // Client error — abort, don't retry
      logger.error('Bad request — not retrying', { status: response.status });
      throw new retry.AbortTaskRunError('Bad request — invalid payload');
    }

    return response.json();
  },
});

// Handle cleanup on failure
export const riskyTask = task({
  id: 'risky-task',
  onFailure: async (payload, error, params) => {
    // Called after all retries are exhausted
    logger.error('Task failed permanently', {
      error: error.message,
      attempt: params.attempt,
    });

    await notifySlack(`Task risky-task failed: ${error.message}`);
  },
  run: async (payload: { jobId: string }) => {
    // Task logic
  },
});

Fix 6: Deploy to Production

# Deploy tasks to Trigger.dev Cloud
npx trigger.dev deploy

# Deploy to a specific environment
npx trigger.dev deploy --env production

# Check deployment status
npx trigger.dev whoami
// Environment variables needed:
// TRIGGER_SECRET_KEY=tr_dev_xxx  (dev)
// TRIGGER_SECRET_KEY=tr_prod_xxx (production)

GitHub Actions deployment:

# .github/workflows/deploy-trigger.yml
name: Deploy Trigger.dev
on:
  push:
    branches: [main]
    paths:
      - 'trigger/**'
      - 'trigger.config.ts'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx trigger.dev deploy
        env:
          TRIGGER_SECRET_KEY: ${{ secrets.TRIGGER_SECRET_KEY }}

Still Not Working?

Task triggers but never runs — the dev server must be running (npx trigger.dev dev). Without it, triggers are queued but no worker picks them up. Check the Trigger.dev dashboard to see if the run is in “queued” or “executing” state. Also verify TRIGGER_SECRET_KEY is set correctly.

“Could not connect” during dev — check your API key and network. The dev server connects to Trigger.dev Cloud via WebSocket. Corporate firewalls or VPNs may block this. Also verify the project ID in trigger.config.ts matches your dashboard project.

Task times out at 300 seconds — increase maxDuration in the task definition. For long-running tasks, break them into smaller sub-tasks using tasks.triggerAndWait() from within a parent task. Each sub-task gets its own timeout.

Changes to task code aren’t reflected — restart npx trigger.dev dev after modifying trigger.config.ts. For task file changes, the dev server should hot-reload, but if it doesn’t, restart it. In production, you must run npx trigger.dev deploy to push changes.

For related background job issues, see Fix: Inngest Not Working and Fix: Wrangler Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles