daaain / daaain/claude-code-log

Cache content digest misses models reached through Any-typed fields

Open
#325 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.2k
Forks
98
Avg merge
3d 2h
Merged PRs (30d)
13

Description

Context

#320 was caused by cache invalidation observing only properties of the source file (mtime, size, subagents fingerprint) and nothing about the shape of what we had written into messages.content. A new model field therefore stayed absent from every blob whose transcript had not changed.

The fix (#322) added a content_version digest derived from the entry models' declared field names, with mismatch or NULL treated as stale.

The residual gap

_content_models() (claude_code_log/cache.py:416) discovers models by walking the entry union through type annotations, unwrapping Optional/union/List/Dict and collecting every reachable BaseModel.

Anything that reaches a content blob without being declared as a typed field is therefore outside the digest:

  • a field annotated Any that carries a model at runtime;
  • a model constructed dynamically;
  • any payload reaching model_dump() from outside the entry tree.

For those, adding a field does not move the digest, and #320's exact failure mode is still live.

Why this is not hypothetical

UserTranscriptEntry.imagePasteIds is itself Optional[Any] (claude_code_log/models.py:273) — the very field whose absence caused #320. So the codebase does use Any where a real shape exists.

Notes

  • This was left open deliberately rather than closed under time pressure; it was named as the sharpest remaining question when the digest work was reviewed.
  • _CONTENT_SCHEMA_SALT (cache.py:399) is the manual escape hatch meanwhile: bumping it invalidates every cache.
  • Hashing types or annotations rather than names was considered and rejected — it would mass-invalidate on a Pydantic or Python upgrade.

Possible directions

Enumerate BaseModel subclasses in the package rather than walking annotations from a root; or narrow the Any-typed fields that actually carry models to real types; or assert in a test that every BaseModel in the package is reachable from the walk, so a new unreachable one fails loudly.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with _content_models() in claude_code_log/cache.py and the Optional[Any] imagePasteIds field in claude_code_log/models.py. Compare the proposed discovery strategies and inspect the existing content_version behavior from #322. Done means an unreachable or runtime-carried model can no longer leave the cache digest unchanged, with a test or explicit coverage assertion for the chosen approach.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.