Skip to content

Add caching for /_skill-validation - #5504

Merged
backspace merged 7 commits into
mainfrom
cache-aware-skill-validation
Jul 16, 2026
Merged

Add caching for /_skill-validation#5504
backspace merged 7 commits into
mainfrom
cache-aware-skill-validation

Conversation

@backspace

Copy link
Copy Markdown
Contributor

The purpose of the /_skill-validation endpoint is to give us an early warning if skills are likely to be broken in the assistant. Since it’s been deployed it’s been flaky (which is why I kept it in the drafts channel):

s 2026-07-15 at 12 16 15@2x

This is because it needs to prerender every tool module, which sometimes reaches Checkly “degraded” status, and occasionally even exceeds the Checkly 30s maximum. We shouldn’t have alarms going off for reasons unrelated to their actual goals.

This PR adds caching of the validation results. A request for /_skill-validation schedules a validation refresh so the next request gets its results. The response includes ageSeconds to indicate when the results are from.

The endpoint prerendered every distinct tool module on every request, so a
frequent monitor spent most polls near the prerender latency and
periodically crossed its degraded/timeout thresholds even when nothing was
wrong. Cache the per-realm result and refresh it off the request path: a
poll serves the last computed result immediately and, once that result is
older than REFRESH_AFTER_MS, kicks a deduped background refresh for the next
poll. Only the first poll after a process start computes synchronously, so
the response stays a definite pass/fail. The response now reports
`ageSeconds` so callers can see result freshness.

Add a `refresh=true` query param that forces a synchronous recompute — used
by the endpoint tests (which share a realm URL) for determinism, and useful
operationally to force a fresh check on demand.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Host Test Results

    1 files      1 suites   2h 39m 27s ⏱️
3 539 tests 3 524 ✅ 15 💤 0 ❌
3 558 runs  3 543 ✅ 15 💤 0 ❌

Results for commit 8863f29.

Realm Server Test Results

    1 files      1 suites   14m 24s ⏱️
1 871 tests 1 871 ✅ 0 💤 0 ❌
1 950 runs  1 950 ✅ 0 💤 0 ❌

Results for commit 8863f29.

@backspace
backspace marked this pull request as ready for review July 15, 2026 18:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a6d25b954

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/realm-server/handlers/handle-skill-validation.ts Outdated
backspace and others added 3 commits July 15, 2026 14:01
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pending.finally(...) returned a second promise that rejected unobserved
when a refresh threw (search/DB error, prerenderer throw), surfacing as an
unhandled rejection that can terminate the process — the caller only
observes `pending` itself. Move the refreshInFlight cleanup into a
try/finally inside the promise body so `pending` stays the only promise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The identity check referenced `pending` inside its own initializer closure,
which TS flags as used-before-assigned. It's also unnecessary: the dedup
guard hands a concurrent caller the existing in-flight promise, so no second
refresh for a realm can be queued while this one runs — the map entry is
always this one when the finally executes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@backspace
backspace requested a review from a team July 16, 2026 12:47
@habdelra
habdelra requested a review from Copilot July 16, 2026 12:48

Copilot AI 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.

Pull request overview

This PR reduces flakiness/latency for the realm-server monitoring endpoint /_skill-validation by caching per-realm validation results and refreshing them off the request path, while also surfacing ageSeconds so monitors can tell how fresh the served result is.

Changes:

  • Add an in-process per-realm cache for /_skill-validation results, with a background refresh once results are older than a refresh threshold.
  • Add refresh=true support to force an on-request recompute.
  • Update the existing endpoint tests to use refresh=true so assertions aren’t affected by cached results.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/realm-server/handlers/handle-skill-validation.ts Introduces cached validation results + background refresh + ageSeconds, and factors validation computation into a helper.
packages/realm-server/tests/server-endpoints/skill-validation-test.ts Adjusts existing tests to request a synchronous recompute via refresh=true.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/realm-server/handlers/handle-skill-validation.ts
Comment thread packages/realm-server/handlers/handle-skill-validation.ts
backspace and others added 3 commits July 16, 2026 11:18
The result cache and in-flight refresh map were module-level singletons
keyed by realm URL, so any two realm servers in one process shared cached
results — cross-contaminating otherwise-isolated test fixtures. Hold both
maps in the handler factory closure instead: production is one server per
process (unchanged), and same-process servers no longer share a cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the caching contract with a counting prerenderer proxy: a cold poll
runs the sweep, a warm poll serves the cache without touching the
prerenderer, and refresh=true forces a recompute. Now that the result
cache is per handler instance, the two existing endpoint tests no longer
need refresh=true to escape a cross-fixture cached result, so drop it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@backspace
backspace merged commit 268e3d1 into main Jul 16, 2026
64 of 65 checks passed
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.

3 participants