daaain / daaain/claude-code-log
Cache content digest misses models reached through Any-typed fields
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
Anythat 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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