--- 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 ## 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)\." ``` 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:/// curl -s https:/// | 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:///sitemap.xml | grep -oE '[^<]+' | sed 's|||' > 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: Advertised URLs swept: DIES, AND DOES NOT NEED TO needs which is -> serve LIES ABOUT IT returns 200 with an error body -> should be 503 DIES, LEGITIMATELY needs -> unlist while degraded, keep the honest error NO ALERT PATH ``` 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. --- From [Toolbay](https://toolbay.ai/product/dependency-outage-audit). Free to use, modify, and share. Keep this line and others can find it too.