Skip to content

fix(renderers): bridge merge must not mutate prior multi_modal_data#104

Merged
hallerite merged 1 commit into
mainfrom
fix/kimi-bridge-mutation
Jul 16, 2026
Merged

fix(renderers): bridge merge must not mutate prior multi_modal_data#104
hallerite merged 1 commit into
mainfrom
fix/kimi-bridge-mutation

Conversation

@hallerite

@hallerite hallerite commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

MultimodalRenderer.bridge_to_next_turn merged the carried-forward multimodal sidecar with dict(previous_multi_modal_data.mm_*) — which copies only the outer dict. The per-modality lists stayed shared with the caller, so appending this turn's items via .setdefault(modality, []).extend(...) mutated the caller's previous_multi_modal_data in place. A reused prior RenderedTokens / MultiModalData would silently grow extra placeholders / items / hashes after a bridge.

Fix: copy the per-modality lists ({k: list(v) for k, v in ….items()}) before extending.

Scope

Cursor Bugbot flagged this on the Inkling PR (#103) for KimiK25Renderer; the identical pattern was in Qwen3VLRenderer and Qwen35Renderer, so all three are fixed here. (Inkling itself was fixed in #103.)

Test

Extends the shared test_multimodal_bridge_extends_and_carries_mm_data with a (2b) assertion that the prior turn's sidecar counts are unchanged after bridging — it now runs green for all five multimodal checkpoints (Qwen3-VL, Qwen3.5, Qwen3.6, Kimi K2.5, Kimi K2.6). Full tests/test_multimodal.py: 85 passed, 3 skipped.

Comment-and-copy only; no token-stream or bridged output change.

🤖 Generated with Claude Code


Note

Low Risk
Targeted copy-before-extend fix with no intended change to token streams; regression covered by extended multimodal bridge test.

Overview
Fixes in-place mutation of the caller's previous_multi_modal_data when multimodal renderers merge carried-forward vision sidecars during bridge_to_next_turn.

Previously, merging used dict(previous_multi_modal_data.mm_*), which only shallow-copied the outer map—per-modality lists stayed aliased, so .setdefault(modality, []).extend(...) could append placeholders, items, and hashes to a reused prior RenderedTokens / MultiModalData. The change deep-copies each modality list ({k: list(v) for k, v in ...}) in Kimi K2.5, Qwen3.5, and Qwen3-VL before extending; bridged token output is unchanged.

Adds test (2b) in test_multimodal_bridge_extends_and_carries_mm_data asserting prior sidecar counts stay (1, 1, 1) after bridging.

Reviewed by Cursor Bugbot for commit a5f0278. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix bridge_to_next_turn mutation of caller-supplied previous_multi_modal_data

The bridge_to_next_turn method in KimiK25Renderer, Qwen35Renderer, and Qwen3VLRenderer was shallow-copying only the outer dict when merging prior multimodal sidecar data, causing .extend() calls to mutate the caller's original per-modality lists. Each renderer now uses {k: list(v) for k, v in ....items()} to copy the inner lists, isolating the merged copy from the caller's data. A regression test in test_multimodal.py now asserts that per-modality counts in previous_multi_modal_data remain unchanged after bridging.

Macroscope summarized a5f0278.

The multimodal bridge_to_next_turn merge built its carried-forward sidecars
with dict(previous_multi_modal_data.mm_*), which copies only the outer dict —
the per-modality lists stayed shared with the caller. Appending this turn's
items via .setdefault(modality, []).extend() then mutated those original
lists in place, so a reused prior RenderedTokens / MultiModalData grew extra
placeholders/items/hashes after bridging.

Copy the per-modality lists ({k: list(v) ...}) in all three affected
renderers — KimiK25Renderer (flagged by Cursor Bugbot on the Inkling PR),
plus Qwen3VLRenderer and Qwen35Renderer, which carried the identical pattern.

Extends the shared test_multimodal_bridge_extends_and_carries_mm_data with a
(2b) assertion that the prior turn's sidecar is unchanged after bridging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@macroscopeapp

macroscopeapp Bot commented Jul 16, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

Straightforward bug fix that replaces shallow dict copies with deep copies of inner lists to prevent mutation of caller data. The change is consistent across 3 renderer files, includes test coverage, and the author is fixing their own code.

You can customize Macroscope's approvability policy. Learn more.

@hallerite
hallerite merged commit bdb96b0 into main Jul 16, 2026
11 checks passed
@hallerite
hallerite deleted the fix/kimi-bridge-mutation branch July 16, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant