Skip to content

feat(cache): negative probe cache — 10-min memo for no-notes chains - #143

Merged
shrwnsan merged 2 commits into
mainfrom
feat/negative-cache
Sep 1, 2026
Merged

shrwnsan merged 2 commits into
mainfrom
feat/negative-cache

Conversation

@shrwnsan

Copy link
Copy Markdown
Owner

Implements the parked idea from the v1.19.0 cache investigation: negative caching with a short TTL for concluded no-notes probes.

The measured problem

Instrumented runs showed a fresh process re-served 27 responses from the on-disk HTTP cache yet still took ~3 minutes. Attribution: the ~10 no-notes packages re-probe their entire non-GitHub chains (up to ~14 URLs with retries each) every run, because failed probes are deliberately never cached — caching "no notes" would freeze the unknown state.

The design (performance memo, never evidence)

  • When fetch_non_github_release_notes concludes nothing for package@version, a 10-minute negative entry is written (HTTP_CACHE_NEGATIVE_TTL_SECONDS=600).
  • The wrapper consults it first: fresh hit → skip the whole chain (zero network).
  • A successful chain clears the entry immediately — regained notes are seen next run.
  • TTL expiry re-probes; --fresh clears it; prune budgets apply; same namespace/hashing as the HTTP cache; no tokens, no provenance, no events.
  • Zero classification impact — these rows stay unknown/unavailable exactly as today; only the wall time changes.

Effect on the motivating case

Immediate re-run: the 10 no-notes packages go from ~14 network attempts each to zero; remaining time is legit evidence fetches. No --fresh semantic changes.

Tests

New tests/test-negative-cache.sh (14 assertions; runner 31 → 32): helper roundtrip/TTL-expiry/corrupt-fails-closed; chain integration with curl-call counting (run 1 probes N URLs → run 2 performs zero); expiry re-probe; success clears; --fresh clears. Three suite-infrastructure gotchas fixed en route (documented in the commit): CACHE_DIR/MAX_RETRIES must be exported before config.sh resolves them readonly; fake-curl arg parsing needs while-shift; json-kind validation means winner fixtures must serve valid JSON.

🤖 Generated with Claude Code - GLM-5.3

Co-Authored-By: Claude noreply@anthropic.com

…s chains

Field-instrumented (2026-08-25): a fresh process re-served 27 responses
from the on-disk HTTP cache, yet the run still took ~3 minutes — the 10
no-notes packages re-probe their full chains every run because FAILED
fetches are deliberately never cached (a cached 'no notes' result would
freeze the unknown state). The dominant cost is the non-GitHub probe
chain: up to ~14 URLs with retries per package.

The negative probe cache memoizes that chain-level outcome, not any
response: when fetch_non_github_release_notes concludes nothing for
package@version, a 10-minute negative entry (HTTP_CACHE_NEGATIVE_TTL_
SECONDS=600) is written; the wrapper consults it first and skips the
entire chain on a fresh hit. A successful chain clears the entry
immediately (regained notes are seen next run), TTL expiry re-probes,
--fresh clears the namespace, pruning budgets apply, and it changes no
classification — these rows stay unknown. It is a performance memo,
never evidence; no tokens, no provenance, no events.

Implementation: three helpers (get/put/clear) beside the HTTP cache in
utils.sh sharing its namespace and hashing; the probe chain renamed to
_fetch_non_github_release_notes_chain with a thin wrapper holding the
hook (all three production call sites route through it; existing test
stubs unaffected).

Tests: tests/test-negative-cache.sh (14 assertions; runner 31 -> 32) —
helper roundtrip/TTL-expiry/corrupt-fails-closed, chain integration with
curl-call counting (first run probes N URLs, immediate re-run performs
ZERO), expiry re-probe, success clears, --fresh clears. Suite gotchas
fixed en route: BREW_CHANGE_CACHE_DIR/MAX_RETRIES must be exported
BEFORE config.sh resolves them readonly; fake-curl arg parsing needs a
while-shift loop (for-loop snapshots desync $2); fetch_url_with_retry
validates kind=json so winner fixtures must serve valid JSON.

🤖 Generated with [Claude Code](https://claude.ai/code) - GLM-5.3

Co-Authored-By: Claude <noreply@anthropic.com>
@shrwnsan
shrwnsan force-pushed the feat/negative-cache branch from 624855a to abcc92c Compare August 27, 2026 10:08
…arisons

The load-based job auto-adjust can halve the pinned BREW_CHANGE_JOBS on
a busy runner between the two compared invocations ('max 2 jobs' vs
'max 4 jobs'), flaking the NO_COLOR byte-identity assertion. Observed
once on the macOS CI runner; three consecutive local runs green after
normalization. Latent since the suite was created — not caused by the
negative cache.

Co-Authored-By: Claude <noreply@anthropic.com>
@shrwnsan

shrwnsan commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

Merge process note (recorded for transparency): maintainer-approved in-session ('Word' on the merge proposal). Same disclosed lift-merge-restore as #122–#142; protection restored immediately after, verified by API diff. Also carries a latent-flake fix in the verdict suite (jobs-line normalization) documented in the commit history.

🤖 Generated with Claude Code - GLM-5.3

@shrwnsan
shrwnsan merged commit 7cb5d6b into main Sep 1, 2026
2 checks passed
@shrwnsan
shrwnsan deleted the feat/negative-cache branch September 1, 2026 02:33
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