How to Fix 'callback not firing' in CrewAI (Python)

By Cyprian AaronsUpdated 2026-04-21
callback-not-firingcrewaipython

Opening

callback not firing in CrewAI usually means your task or agent completed, but the function you expected to run never got invoked. In practice, this shows up when you wire callbacks at the wrong level, pass the wrong callable signature, or use a callback type that CrewAI does not execute in the path you’re testing.

The failure is usually silent: no exception, just missing side effects like logging, persistence, webhook calls, or audit writes.

The Most Common Cause

The #1 cause is attaching the callback to the wrong object or using the wrong signature. In CrewAI, task callbacks and event handlers are not interchangeable, and a plain function that doesn’t match what CrewAI passes will never behave correctly.

Here’s the broken pattern versus the correct one:

BrokenFixed
Callback attached where CrewAI won’t call itCallback attached to Task(callback=...)
Wrong function signatureAccepts the TaskOutput object
Expects arbitrary args like result, agentUses the actual CrewAI callback contract
# BROKEN
from crewai import Agent, Task, Crew

def my_callback(result, agent):
    print("Task finished:", result)

agent = Agent(
    role="Researcher",
    goal="Research company info",
    backstory="You are a diligent analyst."
)

task = Task(
    description="Summarize the latest earnings report.",
    agent=agent,
)

crew = Crew(
    agents=[agent],
    tasks=[task],
    callback=my_callback,  # ❌ Wrong place for task-level callback in most setups
)

crew.kickoff()
# FIXED
from crewai import Agent, Task, Crew

def my_callback(task_output):
    print("Task finished:", task_output.raw)

agent = Agent(
    role="Researcher",
    goal="Research company info",
    backstory="You are a diligent analyst."
)

task = Task(
    description="Summarize the latest earnings report.",
    agent=agent,
    callback=my_callback,  # ✅ Attach directly to the Task
)

crew = Crew(
    agents=[agent],
    tasks=[task],
)

crew.kickoff()

If you’re using a newer CrewAI version with typed outputs, inspect what your callback receives before assuming it’s a string. In many cases it’s a TaskOutput, not plain text.

def my_callback(task_output):
    print(type(task_output))
    print(task_output)

Other Possible Causes

1) You passed the callback but never executed the crew path that triggers it

If you create the crew and forget kickoff(), or call a different flow than expected, nothing fires.

# Broken
crew = Crew(agents=[agent], tasks=[task])
# missing crew.kickoff()

# Fixed
result = crew.kickoff()

2) Your callback raises an exception before printing or persisting anything

CrewAI may complete the task while your callback crashes internally. If you don’t log inside the callback body, it looks like it never fired.

def my_callback(task_output):
    try:
        save_to_db(task_output.raw)
        print("saved")
    except Exception as e:
        print(f"callback failed: {e}")
        raise

Watch for exceptions from your own code:

  • AttributeError: 'TaskOutput' object has no attribute 'text'
  • TypeError: my_callback() missing 1 required positional argument

3) You’re using async code with a sync callback

If your callback needs await, but CrewAI is calling it synchronously in your setup, nothing useful happens unless you bridge it properly.

# Problematic
async def my_callback(task_output):
    await send_webhook(task_output.raw)

Use a sync wrapper if your execution path is sync:

def my_callback(task_output):
    import asyncio
    asyncio.run(send_webhook(task_output.raw))

4) You’re on an older CrewAI version with different callback behavior

Callback support changed across releases. If examples from GitHub or blog posts don’t match your installed version, you can get silent mismatches.

Check your version:

pip show crewai

Then verify against your installed API:

import crewai
print(crewai.__version__)

A mismatch between docs and package version is enough to make Task(callback=...) behave differently than expected.

How to Debug It

  1. Print inside the callback first

    • Add print("callback hit") as line one.
    • If that doesn’t show up, your issue is wiring, not business logic.
  2. Verify where the callback is attached

    • For task completion hooks, attach to Task(callback=...).
    • Don’t assume Crew(callback=...) behaves the same way in every version.
  3. Inspect the actual object passed into the callback

    • Use:
      def cb(x):
          print(type(x))
          print(dir(x))
      
    • This tells you whether you got a TaskOutput, raw string, or something else.
  4. Run with minimal code

    • Remove tools, memory, async calls, and extra agents.
    • Keep only one agent and one task until callbacks fire consistently.

Prevention

  • Keep callbacks small and explicit.

    • Log first.
    • Persist second.
    • Call external services last.
  • Match signatures to real runtime objects.

    • Don’t guess whether you get str, dict, or TaskOutput.
    • Inspect once and codify it.
  • Pin your CrewAI version in production.

    • Callback behavior changes across releases.
    • Treat docs examples as version-specific unless verified locally.

If you want this to stop being a recurring bug in your team, wrap callbacks in a thin adapter layer and test them independently from agent execution. That gives you one place to normalize output shape and handle failures cleanly.


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