---
name: dependency-outage-audit
description: Find which pages die when a dependency dies — database, cache, or upstream API — and which ones lie about it by returning HTTP 200 with an empty body. Use before a launch, after an incident, when adding a page that fetches data, or when the user asks how their app behaves if the database goes down.
---
<!--
  dependency-outage-audit — from Toolbay
  https://toolbay.ai/product/dependency-outage-audit
  Free to use, modify, and share. Keep this line and others can find it too.
-->


# Dependency Outage Audit

## Install

Save this file as `~/.claude/skills/dependency-outage-audit/SKILL.md`, or
`.claude/skills/dependency-outage-audit/SKILL.md` to scope it to one repo. Claude
Code auto-discovers it. Invoke with `/dependency-outage-audit` or by asking "what
breaks if my database goes down?".

## Why this exists

Most apps have never been tested with their database unavailable. The first real
outage is the experiment, it runs in front of customers, and the result is usually
worse than anyone expected: not one broken feature, but every page at once,
because a single unguarded call in a shared layout or helper takes down surfaces
that did not need that data at all.

Two failure modes cost the most, and both are invisible from a status dashboard:

1. **A read-only page dying for a write-path dependency.** Browsing a catalog does
   not need a live database on every request, and yet it usually does.
2. **Failing at HTTP 200.** A page that renders an error boundary still returns
   200. Uptime monitors see success. Crawlers store the empty page as your
   content. You find out weeks later, from a human.

## Step 1 — Inventory what each public page actually needs

```
rg --files -g '**/page.tsx' -g '**/page.jsx' -g '**/+page.svelte' -g 'pages/**/*.tsx'
```

For each page, list its data calls, following helpers rather than trusting names.
A page calling `getProduct()` may pull five queries three levels down.

```
rg -n "await (prisma|db|sql|supabase|fetch|redis)\." <file>
```

Classify each call:

- **ESSENTIAL** — the page is meaningless without it (an order, a user's library).
- **DECORATIVE** — improves the page but is not the point (view counts, related
  items, ratings, seller stats).
- **INHERITED** — pulled in by a shared layout, middleware, or session helper, so
  it hits pages that never asked for it. **These cause the widest outages and are
  the least visible.** Audit shared code first.

## Step 2 — Find the pages that die for decorative or inherited reasons

These are the wins. A catalog page that dies because it also fetches a review
count is trading its entire availability for a number nobody reads.

For each, decide the honest degraded output:

- Real cached or snapshot data, if you have it.
- The section omitted entirely.
- An empty state that is TRUE ("no reviews shown" rather than "0 reviews").

**Never invent a value to fill a gap.** A fabricated count that persists after
recovery is a worse outcome than the outage.

## Step 3 — Check what status code a broken page returns

```
curl -s -o /dev/null -w "%{http_code}\n" https://<host>/<path>
curl -s https://<host>/<path> | head -c 2000
```

Compare status against CONTENT. Flag any 200 whose body is an error boundary, a
spinner, or empty. For streamed server rendering, look for an abort marker with no
completion (in React, a `$RX(` with no matching `$RC(`): the shell streamed, the
data failed, and the error was painted on by client JavaScript **after** the 200
was already sent.

Rules of thumb:

- Page temporarily unable to render real content: **503** with `Retry-After`.
- A sitemap or feed that cannot enumerate: **5xx**, never a valid-but-empty 200.
  An empty sitemap is a positive assertion that your pages are gone.
- Genuinely missing: **404**.

## Step 4 — Simulate the outage, do not imagine it

Reasoning about this is unreliable. Break it on purpose, locally:

```
DATABASE_URL="postgresql://nope:nope@127.0.0.1:1/none" npm run start
```

Point the app at an unreachable host, not a deleted one, so you get connection
failures rather than schema errors. Then walk every public route and record what
each does. Do the same for cache and upstream APIs.

**Do not sample.** Sampling is how the interesting failures survive an audit: the
broken page is rarely the one you would have picked.

## Step 5 — Sweep every URL you advertise

The set that matters most is the set you have publicly promised is there:

```
curl -s https://<host>/sitemap.xml | grep -oE '<loc>[^<]+' | sed 's|<loc>||' > urls.txt
while read -r u; do
  code=$(curl -s -o /dev/null -w '%{http_code}' "$u")
  body=$(curl -s "$u")
  if [ "$code" != "200" ]; then echo "STATUS $code  $u"
  elif echo "$body" | grep -q '\$RX('; then echo "BROKEN RENDER  $u"; fi
done < urls.txt
```

Fetch **every** URL, including nested sitemaps. Pointing a crawler at a page you
know is broken is the same error as an empty sitemap, aimed the other way. If a
route genuinely cannot be served while degraded, unlist it until it can be.

## Step 6 — Verify the guard actually fires

A degradation path that has never run is a guess. After adding one, re-run the
broken-dependency simulation and confirm the page renders. Be especially suspicious
of flags and helpers that decide whether to degrade: if that check silently
evaluates false, it reports success while protecting nothing, and you will believe
it. Assert the degraded state directly, in the environment where it runs.

## Step 7 — Report

```
Pages audited: <n>       Advertised URLs swept: <n>

DIES, AND DOES NOT NEED TO
  <path>  needs <call> which is <decorative|inherited>  -> serve <what>

LIES ABOUT IT
  <path>  returns 200 with an error body  -> should be 503

DIES, LEGITIMATELY
  <path>  needs <call>  -> unlist while degraded, keep the honest error

NO ALERT PATH
  <yes/no: would anyone find out, through a channel that survives the outage>
```

That last line is frequently the real finding. An in-app dashboard, a log nobody
reads, and a job that needs the same database are not alerts.

## Rules

- Simulate; never reason about it from the code alone.
- Sweep every advertised URL, never a sample.
- Degrading is not licence to invent data. Show less, never show fiction.
- If you serve stale or partial data, SAY SO on the page. A store that looks
  healthy while checkout is dead burns more trust than one that admits it.
- Route the alarm through something that does not share the dependency it watches.
