Skip to content

fix(compat): CC 2.1.166 compatibility#81

Open
tombelieber wants to merge 2 commits into
mainfrom
fix/cc-compat-2.1.166-safe
Open

fix(compat): CC 2.1.166 compatibility#81
tombelieber wants to merge 2 commits into
mainfrom
fix/cc-compat-2.1.166-safe

Conversation

@tombelieber

@tombelieber tombelieber commented Jun 6, 2026

Copy link
Copy Markdown
Owner

CC 2.1.166 compatibility audit — SAFE fixes + RISKY findings for review

Automated cc-compat-patch run. Gate fired on a real version bump (baseline 2.1.162 → newest 2.1.166 in local CC data); CHANGELOG for (2.1.162, 2.1.166] read in full. The version delta itself was transcript-shape-clean (settings/permissions/CLI/hook-output/runtime only), but the full 3-dimension regression sweep surfaced 7 confirmed findings (0 rejected). None were introduced by the 2.1.x bump — they are pre-existing gaps the sweep quantified against real data.

✅ SAFE — applied in this PR (verified)

1. fix(history) — recover ~2.4% of silently-dropped history.jsonl lines (203ebd8c)

  • CC elides the paste body for large pastes, persisting only {"id":1,"type":"text","contentHash":"…"} with no content field. PasteContent.content lacked #[serde(default)], so serde rejected the whole PromptEntry and parse_history silently dropped the entire line (debug!("skipping malformed history line")).
  • Real corpus: 490 / 20343 lines (2.41%) dropped → undercounted prompt stats + those prompts unsearchable in the Prompts UI.
  • Fix: #[serde(default)] on every PromptEntry/PasteContent field (External-data boundary-normalization mandate). Deserialize-only internal types (no TS/utoipa derive) → no codegen. +2 regression tests.
  • Verify: cq check ✅ · cq clippy -D warnings ✅ · prompt_history tests 13/13 ✅ · real-file parse 19853→20343 (0 dropped).

2. fix(cc-compat) — oracle now censuses nested tool_result.content[] (b3fba699)

  • cc_compat_oracle::scan_file only collected top-level message.content[] types, treating tool_result as a leaf. But tool_reference appears only nested (815× in real data, 0× top-level), so the drift gate was structurally blind to it — it reported unhandled_content_block_types: [] while the type is genuinely dropped by the parsers. This is why this run's initial drift signal looked clean.
  • Fix: descend exactly one level into tool_result.content[] (mirrors extract_tool_results); never a deep walk (a deep walk over-collects 9 junk type keys from tool_use.input). Isolated to the standalone diagnostic binary — no parser/export/live/frontend impact.
  • Verify: patched oracle flips unhandled_content_block_types []['tool_reference'] (true-positive). cq check/clippy -D warnings ✅.

⚠️ Merge-ordering note (genuine decision): finding #2 makes the detector honest, so after it merges the daily cc-compat gate's branch 2 (unhandled_* non-empty → live gap → audit) will re-fire every run until the tool_reference handling below (RISKY #1/#2) lands. Recommended: land the tool_reference + nested-image handling in the same merge as this PR, or accept that the gate correctly re-flags until then. (The fix is correct on its own; this is purely about avoiding repeated audits of a known-open gap.)

🔧 RISKY — needs human (full evidence below; not auto-applied)

These are real, data-proven Zero-Data-Loss issues but require multi-crate data-model changes, codegen, and/or UI/UX decisions — out of scope for additive auto-apply.

R1 (high) — Nested tool_result.content[] image + tool_reference dropped on the main timeline path.
crates/core/src/block_accumulator/content.rs extract_tool_results descends one level but filters to text only. ToolResultBlock has no carrier for images or tool_reference. Real data: nested text 923 / tool_reference 815 / image 454 (still present at 2.1.165/166: tool_reference 13, image 9). tool_reference is always ToolSearch output (matched/loaded tool names) — so every ToolSearch tool-result currently renders blank; computer-use/browser/Read-of-image screenshots render empty.
Fix spans: block_accumulator/{content,assistant,handlers}.rs (+images: Vec<ImageContent>, tool_refs: Vec<String> on ToolResultBlock/ToolResult), crates/types/src/block_types.rs, ts-rs regen + OpenAPI regen (new fields), ToolDetail.tsx (render thumbnails + matched-tools chips — base64-thumbnail-vs-inline is a UX call). Add "tool_reference" to handled_content_block_types() only once actually surfaced.

R2 (high) — Same drop on the simple-parser export/share path (/parsed → Markdown/PDF/HTML/share).
crates/core/src/parser/content.rs extract_tool_result_content joins only nested text; crates/core/src/parser/session.rs extract_images scans top-level only (returns 0 for the 454 nested images). Image portion is purely additive (reuses the existing Message.images channel — no codegen); tool_reference portion changes the result string that feeds the server-side share serializer + the byte-identical block-pipeline export oracle, so it must be validated against that oracle first. Fix both render paths together (the "two render paths" drift rule).

R3 (high) — mode SystemVariant renders nothing in BOTH chat & dev timelines (2,033 real records).
Backend correctly emits SystemVariant::Mode and the generated SystemVariant.ts has 'mode', but the hand-written packages/shared/src/types/blocks.ts union is missing 'mode', so the _exhaustive: never guards in both dispatchers compile as if the switch were exhaustive and both fall to default → null. Classic ts-rs-regen hand-type drift (introduced 2026-06-02 when Mode was added Rust-side). Fix: add 'mode' to the union (this intentionally breaks the two exhaustiveness guards, surfacing the missing arms), add a ModePill reading data.mode (not data.permissionMode) + a dev arm, +frontend tests. Hardening: derive blocks.ts union from generated SystemVariant so this can't silently recur. bun typecheck + shared vitest; no Rust/codegen.

R6 (low) — Simple parser drops 9/15 record types on the export/share path (mode, permission-mode, attachment, pr-link, worktree-state, ai-title, custom-title, agent-name, last-prompt).
crates/core/src/parser/session.rs matches only 6 types; the rest hit _ => debug!("Ignoring …"). The main timeline (?format=block, block_accumulator) handles all 15 — asymmetric fidelity ("share path is lossy"). Mostly session metadata (conversation turns are preserved). Preferred systemic fix: route export/share through the block_accumulator (single source of truth) so exports become byte-equivalent to the timeline; smaller fix: add the 9 missing arms + a parser-parity guard test. Both need a shape/UX decision (e.g. avoid cluttering exports with 81k attachment blocks).

Baseline

Baseline bumped 2.1.162 → 2.1.167 via cc-compat-oracle --update-baseline (all gaps are either applied here or captured above, per the skill's Step 5). The audit covered the (2.1.162, 2.1.166] delta; 2.1.167 landed mid-run and was confirmed transcript-shape-clean (changelog: "bug fixes and reliability"; empirical scan of 216,744 records including 2.1.167 → 0 unhandled record types, unchanged 16-type/5-block inventory) — so it is not an unaudited blind bump. This stops the version-gate from re-auditing the same delta. See the merge-ordering note re: branch-2 re-fires after #2 merges.

Drift report (oracle)

🤖 Generated with Claude Code

tester and others added 2 commits June 7, 2026 03:55
CC elides the paste body for large pastes, persisting only a contentHash
with no `content` field. PasteContent.content lacked #[serde(default)], so
serde rejected the whole PromptEntry and parse_history silently dropped the
entire line — ~2.4% of a real history.jsonl corpus (490/20343), undercounting
prompt stats and making those prompts unsearchable.

Add #[serde(default)] to every PromptEntry/PasteContent field per the
External-data boundary-normalization mandate: a missing/renamed field now
degrades per-field instead of dropping the line. Deserialize-only internal
types (no TS/utoipa derive) → no codegen. +2 regression tests; 13/13 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scan_file only collected top-level message.content[] block types, treating
tool_result as a leaf. But tool_reference appears ONLY nested inside
tool_result.content[] (815x in real data, 0x top-level), so the drift gate
was structurally blind to it — reporting unhandled_content_block_types: []
while the type is genuinely dropped by the parsers.

Descend exactly ONE level into tool_result.content[] (mirroring
extract_tool_results), never a deep walk (which over-collects junk type keys
from tool_use.input). Oracle now correctly surfaces unhandled=[tool_reference].
Isolated to the standalone diagnostic binary; no parser/export/live impact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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