How to Fix 'callback not firing in production' in CrewAI (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
callback-not-firing-in-productioncrewaitypescript

When CrewAI says callback not firing in production, it usually means your agent/tool ran, but the callback handler never executed in the deployed runtime. In TypeScript, this often shows up only after deployment because local dev and production differ in bundling, async timing, environment variables, or serverless execution.

The pattern is usually simple: the callback is registered correctly in code, but something about how the app runs in production prevents the event from reaching it. The failure is often silent unless you log inside the callback or inspect the agent run output.

The Most Common Cause

The #1 cause is passing a callback that gets lost because of incorrect async wiring or because the object is recreated before the run completes.

In CrewAI TypeScript integrations, this usually happens when you create an Agent, attach a callback, and then call kickoff() without awaiting the promise or without keeping the same instance alive long enough for the callback to fire.

Broken patternFixed pattern
Callback defined, but run is not awaitedAwait the crew run and keep the instance stable
Callback attached to a temporary objectPass a persistent callback reference
// BROKEN
import { Agent, Crew } from "crewai";

const agent = new Agent({
  name: "SupportAgent",
  role: "Support",
  goal: "Handle customer tickets",
  callbacks: [
    {
      onToolStart: (tool) => {
        console.log("Tool started:", tool.name);
      },
    },
  ],
});

const crew = new Crew({ agents: [agent] });

// kickoff() returns a Promise, but nothing awaits it.
// In production/serverless this can exit before callbacks run.
crew.kickoff({ input: "Check policy status" });
// FIXED
import { Agent, Crew } from "crewai";

const supportCallback = {
  onToolStart: (tool: { name: string }) => {
    console.log("Tool started:", tool.name);
  },
};

const agent = new Agent({
  name: "SupportAgent",
  role: "Support",
  goal: "Handle customer tickets",
  callbacks: [supportCallback],
});

const crew = new Crew({ agents: [agent] });

async function main() {
  const result = await crew.kickoff({ input: "Check policy status" });
  console.log("Crew result:", result);
}

main().catch((err) => {
  console.error("CrewAI kickoff failed:", err);
});

If you’re running on serverless platforms like Vercel Functions, AWS Lambda, or Cloud Run with short request lifetimes, this matters even more. The runtime can terminate before onToolStart, onTaskComplete, or custom callback hooks flush logs or finish async work.

Other Possible Causes

1. The callback method name does not match CrewAI’s expected hook

A typo like onToolstart instead of onToolStart means your handler compiles but never runs.

// BROKEN
const callbacks = {
  onToolstart: () => {
    console.log("This will never fire");
  },
};
// FIXED
const callbacks = {
  onToolStart: () => {
    console.log("This will fire");
  },
};

Use exact hook names from the version of CrewAI you installed. If you upgraded packages and kept old hook names, production may silently ignore them.

2. The callback throws before logging anything useful

If your handler throws early, it can look like it never fired at all.

// BROKEN
const callbacks = {
  onTaskComplete: (taskResult: any) => {
    // taskResult may not have this property
    console.log(taskResult.output.text.toUpperCase());
  },
};
// FIXED
const callbacks = {
  onTaskComplete: (taskResult: any) => {
    console.log("Task completed payload:", taskResult);

    const text = taskResult?.output?.text;
    if (!text) return;

    console.log(text.toUpperCase());
  },
};

In production, guard every nested property access. A thrown exception inside a callback often gets swallowed by surrounding orchestration code.

3. Environment-specific config is missing in production

A missing API key or disabled feature flag can stop the underlying run before callbacks execute.

# Example missing env vars in prod
CREWAI_API_KEY=
ENABLE_AGENT_CALLBACKS=false
if (process.env.ENABLE_AGENT_CALLBACKS !== "true") {
  throw new Error("Callbacks are disabled in this environment");
}

Check for differences between .env.local, staging secrets, and production secrets. A lot of “callback not firing” bugs are actually “agent never reached that path.”

4. You are using a non-serializable callback in a bundled runtime

Some bundlers strip functions passed through JSON boundaries or isolate them during edge execution.

// BROKEN
const configFromJson = JSON.parse(process.env.CREW_CONFIG ?? "{}");
// functions cannot survive JSON serialization
configFromJson.callbacks.onTaskComplete = () => console.log("lost");
// FIXED
const callbacks = {
  onTaskComplete: () => console.log("kept as code"),
};

const crewConfig = {
  ...JSON.parse(process.env.CREW_CONFIG ?? "{}"),
  callbacks,
};

If your deployment serializes config through JSON, keep executable hooks in code, not in env-driven blobs.

How to Debug It

  1. Log at every boundary

    • Log before kickoff()
    • Log inside each callback hook
    • Log after await kickoff()
    • If you see pre-run logs but not callback logs, the issue is runtime flow or hook registration
  2. Verify the exact hook name and class shape

    • Confirm your installed CrewAI TypeScript version
    • Check whether you’re using Agent, Crew, Task, or tool-level callbacks
    • Compare against docs for that version; mismatched names are common after upgrades
  3. Test with a minimal reproduction

    • Remove all tools except one trivial tool
    • Use one agent and one task
    • Replace real business logic with console.log
    • If it works locally but fails in prod, focus on deployment/runtime differences
  4. Inspect production logs for thrown errors

    • Search for:
      • TypeError
      • Cannot read properties of undefined
      • UnhandledPromiseRejection
      • CrewAI task execution errors around kickoff()
    • A callback that throws can look identical to one that never fired

Prevention

  • Always await crew.kickoff() and keep callback handlers synchronous unless you explicitly need async work.
  • Keep callbacks as plain code objects in source control; don’t build them from serialized JSON config.
  • Add a smoke test that asserts callback execution:
expect(callbackSpy).toHaveBeenCalled();

If you want one rule to remember: when a CrewAI callback works locally but not in production, assume lifecycle or deployment behavior first, not CrewAI itself. In TypeScript apps, the bug is usually around how the process ends, how config is loaded, or how exceptions are handled inside the hook.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides