Skip to content

update the age header when responding from cache - #15052

Open
asharpe wants to merge 6 commits into
cloudflare:mainfrom
asharpe:cache-hit-age
Open

update the age header when responding from cache#15052
asharpe wants to merge 6 commits into
cloudflare:mainfrom
asharpe:cache-hit-age

Conversation

@asharpe

@asharpe asharpe commented Aug 6, 2026

Copy link
Copy Markdown

Fixes #[insert GH or internal issue link(s)].

When responding from the cache (HIT), update the Age header to be relative to the time the object was stored.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because:

A picture of a cute animal (not mandatory, but encouraged)


Open in Devin Review

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 869414f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
miniflare Patch
@cloudflare/deploy-helpers Patch
@cloudflare/pages-shared Patch
@cloudflare/remote-bindings Patch
@cloudflare/runtime-types Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch
wrangler Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
workers-devprod requested review from a team and NuroDev and removed request for a team August 6, 2026 04:47
@workers-devprod

workers-devprod commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/increment-cache-response-age.md: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/cache/cache.worker.ts: [@cloudflare/wrangler]

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread packages/miniflare/src/workers/cache/cache.worker.ts Outdated
Comment on lines +306 to +309
const now = this.timers.now();
const age = parseInt(resHeaders.get("age") || "0", 10);
const cachedDuration = Math.round((now - (cached.metadata.stored || now)) / 1000)
resHeaders.set("Age", age + cachedDuration);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Behaviour change to cached response headers ships without any test coverage

The change that rewrites the age of responses served from the local cache (resHeaders.set("Age", ...) at packages/miniflare/src/workers/cache/cache.worker.ts:309) has no accompanying test, which the repository requires for new functionality.
Impact: A regression in the reported age of cached responses could ship unnoticed.

Repository testing requirement

CONTRIBUTING.md ("PR Tests") states: "Every PR should include tests for the functionality that's being added", and Miniflare tests live in packages/miniflare/test (e.g. test/plugins/cache/index.spec.ts). The PR modifies cache HIT behaviour and stores a new stored metadata field but adds no spec covering either the incremented age or the fallback for entries stored before this change.

Prompt for agents
Add a Miniflare test (packages/miniflare/test/plugins/cache/index.spec.ts, using the existing miniflareTest harness and the fake timers utilities) verifying that a response served from the cache reports an age equal to the original age plus the elapsed time since it was stored, and that entries whose stored metadata is absent (written by an older version) keep their original age.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

headers: Object.entries(headers),
status: res.status,
size,
stored: this.timers.now(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Cached responses can report an age that is too young for slow or large uploads

The time a cached item was saved is recorded (this.timers.now() at packages/miniflare/src/workers/cache/cache.worker.ts:366) only after the whole body has finished being written, rather than when the item was received, so slow or large responses are later reported as newer than they are.
Impact: Clients can be told a cached response is fresher than it really is, which can make them hold on to stale content longer.

Why the timestamp lands late: metadata promise resolves after the blob write

metadata is a promise created from sizePromise (packages/miniflare/src/workers/cache/cache.worker.ts:355-367). KeyValueStorage.put() first awaits this.#blob.put(entry.value) and only then awaits entry.metadata (packages/miniflare/src/workers/shared/keyvalue.worker.ts:208-223), so this.timers.now() inside the .then() executes after the entire body has been streamed. By contrast, the entry's expiration is computed synchronously at request time (packages/miniflare/src/workers/cache/cache.worker.ts:372), so stored and expiration use different clocks. Capturing the timestamp once, before starting the stream, and reusing it for both would keep them consistent.

Prompt for agents
In packages/miniflare/src/workers/cache/cache.worker.ts, the `put` handler records `stored: this.timers.now()` inside the metadata promise (`sizePromise.then(...)`). Because `KeyValueStorage.put()` awaits the blob write before awaiting the metadata promise (packages/miniflare/src/workers/shared/keyvalue.worker.ts), this timestamp is taken after the whole response body has been streamed, not when the response was received. The `expiration` passed to `storage.put` is computed synchronously at request time, so the two values are based on different clock readings; for large or slow bodies the entry's recorded store time is later than its effective expiry basis, making the Age header computed in `match` too small. Consider capturing a single `now` value before starting the stream and using it for both `stored` and `expiration`.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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