·
Last changedVersion:
No hardening report available.
This skill walks through a specific endpoint that is slow, expensive, or unreliable, and produces
a concrete recommendation. It is the deep-dive counterpart to auditing-endpoints (which finds
candidates).
auditing-endpoints as a failing materialisation or expensive callerIf the question is project-wide ("what should I clean up?"), use auditing-endpoints first.
| Tool | Purpose |
|---|---|
| Full endpoint config: query, current version, |
| History of every version (query + materialisation state); which version is current |
| Whether materialisation is eligible, current state, last run, last error |
| What the materialised query would look like, plus the rejection reason if ineligible |
| When was it last called (endpoint-level sanity-check that it is in active use) |
| Query |
When deciding what to recommend, walk these in order — the first one that applies is the cheapest fix.
Fetch the endpoint and look at data_freshness_seconds (it sets both the cache TTL and, when
materialised, the refresh cadence). If the user's traffic
calls the same parameters repeatedly within that window, every call after the first is a cache
hit and effectively free.
data_freshness_seconds is an
enum: 900, 1800, 3600, 21600, 43200, 86400, 604800 — there is no sub-15-minute value.)The shape of the variables matters here: if every call passes different user_id or date_from
values, the cache has many distinct keys and a higher TTL helps less. If almost every call uses
the same handful of parameter combinations, the cache helps a lot. Confirm the acceptable staleness
with the user before recommending any TTL increase or materialisation.
Materialisation pre-computes the query into a saved view that's refreshed on a schedule. Reads become near-instant — at the cost of staleness equal to the refresh interval, plus storage and compute for the materialisation itself.
Call endpoints-materialization-preview. The response tells you:
When materialisation is enabled, callers must pass all materialised variables — calls without them are rejected (security: prevents returning unfiltered data). Pair the recommendation with a note about which variables become required.
For a SQL endpoint that isn't eligible, the rejection reason from
endpoints-materialization-preview is the lead. That tool always resolves. Read the reason and
match it to the bullets below — they summarise the common cases, not every check the server runs,
so the raw reason wins on the technical eligibility question when the two disagree. Propose the rewrite by hand:
JOIN plus a variable filter is rejected for
materialisation, because applying the variable changes the joined row cardinality and silently
produces wrong results (e.g. LEFT JOIN non-matches lose the variable column). Restructure so the
variable filters a single table — push the filter into a subquery/CTE that's then joined, rather
than filtering across the join. This is the most common "looks fine but won't materialise" trap.date_from, lookback_days).* / non-deterministic functions → narrow the columns selected, replace
now() / today() with a variable when possible.OR, a variable compared against another variable, a variable in a
HAVING clause, an unsupported operator, a query kind that can't be materialised). Quote the
rejection reason to the user verbatim in a separate quote block and reason from it directly rather than forcing the nearest
bullet. Some reasons have no equivalent rewrite at all — the OR {variables.x} = 'all'
optional-variable idiom is one — so say that instead of changing the query's behaviour.Check endpoint-versions to see whether the query was recently changed. Often the regression
came from a specific commit and reverting that version is faster than rewriting. Before proposing a
rollback, compare the target version's query with the current one and point out any filters,
variables or fixes the older version lacks; present the rollback as a proposal for the user to
approve.
For every proposed rewrite, state how its results differ from the current query and confirm that no filter is removed or loosened; where feasible, compare old and new results on a few sample parameter values before asking for sign-off.
Only the latest version runs by default; older versions run only when a caller pins ?version=N.
So the version to tune is almost always the current one — unless a pinned older version is the
culprit. Call endpoint-versions and read each version's last_executed_at to see which versions
have been hit recently; a materialised version with a null or long-stale last_executed_at is a
possibly unused rather than a tuning target (that signal only counts API-key runs and can be sparse,
so state the time window it covers). Ask the user to confirm that no callers pin ?version=N, prefer
unmaterialising because it is reversible, and delete a version only when the user explicitly approves
that named version.
For endpoint-level call frequency and per-call cost, query query_log with execute-sql — it
carries query_duration_ms, read_rows, and read_bytes, handy for confirming how heavy the
endpoint's calls actually are. Fill the endpoint name placeholder only with the canonical name
returned by endpoint-get, and only when it contains just letters, digits, underscores and hyphens;
otherwise stop and ask the user. Use execute-sql here only for read-only aggregate queries against
query_log for this one endpoint, limited to a recent time window agreed with the user, and return
only counts, timings and sizes rather than raw query text, parameters or user identifiers:
/api/projects/{team_id}/endpoints/{name}/run. If the project number in that URL differs from the
active project, stop and ask the user before querying anything. Use the canonical name that
endpoint-get returns for every later step.endpoint-get to fetch full config. Note: data_freshness_seconds, is_materialized,
current version, query kind.endpoint-materialization-status to see materialisation health (failed? eligible?).endpoint-versions to see the query history and which version is current (the one callers
hit unless they pin ?version=N).endpoint-get's last_executed_at is endpoint-level recency;
endpoint-versions gives each version's own last_executed_at; query_log (via execute-sql)
gives endpoint-level call frequency and per-call cost. All count only personal-API-key calls, and
per-version recency can be sparse — confirm with the user before calling a version dead.agent-feedback. Show the user the exact message first and send it only
after they agree; keep it to a generic description of the limitation, leaving out query bodies,
endpoint names, variable values, team IDs and usage data.Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.cgr.dev/public/github.com/posthog/skills/omnibus/diagnosing-endpoint-performance:latest
MIT
HARDENING.md
LICENSE
SKILL.md