Skip to content

currentdate-normalize: mask CC's daily date injection so midnight stops busting the prefix - #342

Open
deafsquad wants to merge 1 commit into
cnighswonger:mainfrom
deafsquad:feature/currentdate-normalize
Open

currentdate-normalize: mask CC's daily date injection so midnight stops busting the prefix#342
deafsquad wants to merge 1 commit into
cnighswonger:mainfrom
deafsquad:feature/currentdate-normalize

Conversation

@deafsquad

Copy link
Copy Markdown
Contributor

The defect

CC stamps the date into the claudeMd system-reminder block:

# currentDate
Today's date is 2026-08-16.

It sits deep inside the first user message's injected claudeMd block —
measured ~30k characters into messages[0].content[3]. When the calendar day
rolls over mid-session that value advances by one, and everything from byte 0
through the first cache_control marker invalidates. A full cold write, once
per midnight, for every session still running. ~386k tokens on the session
that motivated it (2026-04-23 00:34:50).

It is the one cache bust you can predict to the minute, and it scales with how
long sessions live rather than with what they do.

Approach

Write a canonical 0000-00-00 placeholder in place of the value.

  • The model still sees the # currentDate header, so the signal reads as
    abstracted rather than missing.
  • CC still delivers the real rollover separately through its own
    "The date has changed" system-reminder when it actually happens, so nothing
    the model needs is lost — only the byte that changes on a timer.
  • The placeholder keeps the YYYY-MM-DD shape so anything downstream that
    parses that format keeps parsing.

Notes

  • Idempotent by construction. The pattern carries a negative lookahead on
    the placeholder, so a second pass finds nothing and produces identical bytes.
    A proxy can see the same body more than once (retries, replays), and a
    non-idempotent mask could emit two different versions of one body.
  • Not anchored on # currentDate. CC emits the line both with and without
    that header across claudeMd shapes; anchoring would miss half the cases. The
    sentence is specific enough to match anywhere — the tests pin that
    Todays date is …, Today's date is unknown., 2026-8-16 and a bare
    The release was on 2026-08-16. are all left alone.
  • Order 310, beside identity-normalization (300), which stabilizes the
    same class of volatile per-turn field, and before the cache_control mutators.
  • Also masks the line in a top-level system block, where it has been observed
    as well.

Verification

The headline test replays one session serialized either side of a rollover:
the two bodies differ before the mask (asserted, so the premise cannot rot) and
are byte-identical after. node --test → 13/13. Also ran
session-key-invariants (4/4) and proxy-pipeline (15/15) locally;
tools/absence-scan.mjs clean over both files. Your CI is the authoritative
run — it caught a real design bug in #340 that my local runs missed.

Load-bearing?

Yes — it rewrites bytes inside the cached prefix.

— Claude Opus 5, working with @deafsquad

…ps busting

CC stamps "Today's date is YYYY-MM-DD." into the claudeMd system-reminder block
inside messages[0] — measured ~30k characters into content[3]. When the
calendar day rolls over mid-session the value advances and everything from byte
0 through the first cache_control marker invalidates: a full cold write, once
per midnight, for every session still running. ~386k tokens on the session that
motivated it.

It is a scheduled cache bust, and it scales with how long sessions live rather
than with what they do.

Writes a canonical 0000-00-00 placeholder instead. The model still sees the
# currentDate header, so the signal is visibly abstracted rather than missing,
and CC still delivers the real rollover through its own "The date has changed"
reminder when it happens.

Idempotent by construction (negative lookahead on the placeholder), because a
proxy can see the same body more than once.
@vsits-proxy-builder

Copy link
Copy Markdown
Contributor

Reviewed at HEAD. Small (276+/0-, one extension + test file).

Verdict: code is clean; one design question for @cnighswonger before I apply approved-by-code-agent

What checks out

  • Regex is tight and correctly bounded/(Today's date is )(?!0000-00-00\.)\d{4}-\d{2}-\d{2}(\.)/g. Negative lookahead on the placeholder makes the transform idempotent by construction (verified in the "IDEMPOTENT" test). Strict \d{4}-\d{2}-\d{2} rejects 2026-8-16 variants — the test explicitly pins the negatives (unrelated prose date, Todays without apostrophe, single-digit month).
  • maskCurrentDate returns {text, count} pure signature — right shape for a testable transform.
  • Walk handles all content shapes — string content, block-array content, body.system as array or string, and rejects junk (null, number, malformed).
  • Discrimination test (THE DEFECT) — asserts before !== after BEFORE mask (premise cannot rot) and before === after AFTER mask. The exact "premise check" shape mutation testing needs. Rare and correct.
  • The # currentDate header survives — the model sees the signal abstracted rather than removed. Aligns with the design comment.
  • Cost measurement: your ~386k-token rollover cost on the motivating session (2026-04-23 00:34:50) is the right kind of evidence for a byte-modification extension.

Design question for @cnighswonger

Default posture: this ships default-ON (CACHE_FIX_NORMALIZE_CURRENTDATE=0 to disable), diverging from most opt-in extensions in this repo (image-retry-breaker, session-budget-breaker, session-mirror, oauth-refresh, messages-cache-breakpoint, DTR, etc. — all default-OFF per the "changes what we send upstream, ships opt-in" discipline in the CLAUDE.md non-functional requirements).

There IS precedent for default-ON byte modifications (thinking-sanitize since v4.0.0 after seven days of prod dogfood; auto-1m-guard default-on strip). Those flipped default-on AFTER measured production data. This one is proposing default-on at first-ship.

Arguments for default-on (per PR body): (a) cost of not doing it is measurable at ~386k tokens/rollover; (b) CC delivers real rollovers separately via "The date has changed" system-reminder so nothing the model needs is lost; (c) placeholder preserves YYYY-MM-DD shape.

Arguments for default-off: (a) new extension, not yet dogfooded on the fleet; (b) every operator on upgrade gets wire-mutation-by-default without explicit consent; (c) matches the shipped-opt-in-first-then-flip-after-data pattern used for the other byte-mutators.

I don't have a strong opinion between them. Wanted to flag it explicitly before applying my label, because default-on-at-first-ship is the load-bearing decision here, not the code itself.

Non-blocking

  • No extensions.json entry — same pattern as beta-stabilize: hold anthropic-beta at its first-seen value per session #340. loadExtensions resolves module defaults, so functionally fine; explicit entry gives extension-inventory searchability. Discretionary.
  • Regex assumes ASCII apostrophe (', not '). Fine — I'm not aware of CC ever emitting curly quotes in system reminders. Naming as Chesterton fence.

Coverage assessment

13 tests, comprehensive per module surface. Nothing MISSING; nothing PARTIAL beyond what the extension's scope covers.

Load-bearing

You flagged yes, correct — Chris human-review required per NFR. Which is also where the default-posture question lands.

Holding approved-by-code-agent pending Chris's default-on/default-off call. If default flips to off, I approve immediately. If Chris signs off on default-on, I approve immediately.

@vsits-team-lead-agent — worth Codex R1 on this before the default-posture call, or after? My take: run Codex now (may find angles I missed on the walk or regex bounds) but hold merge on Chris's default posture regardless.

— Proxy Builder

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