Skip to content

Pin the compaction summary at the request's cache lifetime - #381

Merged
lydakis merged 1 commit into
mainfrom
claude/project-thread-vhw7x5
Oct 29, 2026
Merged

lydakis merged 1 commit into
mainfrom
claude/project-thread-vhw7x5

Conversation

@lydakis

@lydakis lydakis commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Requested by George · project thread

Daemon change (bug fix). On a bot set to the hour-long Anthropic cache (cache_ttl = "1h", Sonny's default for Claude bots since #318), every call after the bot's first compaction was refused with a 400.

Cause

The request's own breakpoints (instructions, and the top-level one) took ttl: "1h", but the pinned compaction summary and carry-forward note kept {"type":"ephemeral"}, which the API reads as five minutes (src/store/context.rs). That makes 1h → 5m → 1h, and Anthropic refuses a longer lifetime after a shorter one ("a 1-hour cache entry must appear before any 5-minute cache entries").

Fix

Every breakpoint in a request now takes the same lifetime, as pi computes one cache marker per request and uses it at every breakpoint (opencode never sets a TTL, so it never mixes them either).

  • codec::cache_control(hour) is the one marker; the provider's markers and pinned_item both use it.
  • The turn picks the lifetime once, in its settings: the bot's, unless the turn's model, its summarizer (which copies the turn's calls with their pinned blocks) or its fallback (which answers them) must keep five minutes (older Claude on Bedrock), in which case they all do. Provider::takes_cache_hour exposes that rule.
  • Store-side size estimates use the bot's setting, or the longer marker where they only bound the size.

Bots already stuck recover on their next turn. The pinned blocks are rendered from the stored summary on every request, so nothing on disk holds the bad marker. The one exception is a paced summary copy parked with its prefix: its retry fails once, is reported as compaction_failed, and the turn goes on. Their next call also replays older thinking without it once, since the prefix bytes change.

No retry on a cache_control 400: no harness we checked has one, the error would have to be matched by its text, and requests are now correct by construction.

Tests

  • The Anthropic fixture now refuses misordered lifetimes with a 400, as the API does.
  • test_an_hour_long_cache_pins_the_summary_for_an_hour_too: a 1h bot compacts and keeps completing turns, and every pinned summary is marked 1h. It fails when the summary's marker is forced back to five minutes.
  • test_a_bot_moved_to_an_hour_after_its_summary_keeps_working: compacts at 5m, moves to 1h, and the next turn completes with the summary at 1h.
  • one_model_that_keeps_five_minutes_keeps_every_request_at_five and pinned_blocks_cache_only_with_their_provider_format (both lifetimes).
  • cargo fmt --check and cargo clippy --workspace --all-targets -D warnings are clean.

Generated by Claude Code

On a bot set to the hour-long Anthropic cache, the pinned compaction
summary and carry-forward note kept a breakpoint with no lifetime, which
the API reads as five minutes. Instructions (1h), then the summary (5m),
then the request's own breakpoint (1h) breaks the rule that a longer
lifetime may not follow a shorter one, so every call after a bot's first
summary was refused with a 400.

Every breakpoint in a request now takes one lifetime, as other harnesses
compute one cache marker per request and use it everywhere. The turn
picks it once: the bot's setting, unless the turn's model, its summarizer
(which copies the turn's calls) or its fallback (which answers them) must
keep five minutes, in which case they all do. The pinned blocks are
rendered from the stored summary on every request, so a bot already stuck
this way recovers on its next turn with no repair.

The Anthropic test fixture now refuses misordered lifetimes as the API
does; the new compaction tests fail without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGEPbASx5sbXvPAfKHw
@lydakis lydakis self-assigned this Oct 29, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T23:28:47.086776Z 2edaa36 PR opened
🔒 Security Review ✅ Completed 2026-10-29T23:29:24.947447Z 2edaa36 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

lydakis commented Oct 29, 2026

Copy link
Copy Markdown
Owner Author

The advisory macos job failed in lookout::goals::tests::a_check_says_its_code_and_the_end_of_what_it_printed (sonny/src/lookout/goals.rs:779). The check that prints 50 MB came back with no exit code. This PR doesn't touch Sonny, and the same job passed on main at 20701b1.

I think it's timing or isolation in that test, not this change. The check gets a 5 s timeout on a shared runner. Also, goals::stop() SIGKILLs every process group in a static list, so another test running in parallel could kill this test's check, which also gives None. No fix exists yet, and I can't re-run jobs from this session. The required rust and runtime jobs decide the merge here.


Generated by Claude Code

@lydakis
lydakis merged commit a8534bd into main Oct 29, 2026
4 of 5 checks passed
@lydakis lydakis mentioned this pull request Oct 30, 2026
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.

2 participants