Measure how long pins stood stale
manni cite check says whether each pin holds now. It cannot say how long a
pin had failed before someone noticed, or which releases shipped the page
meanwhile. manni cite history answers both, from git alone. This page runs
it on one page, then from a scheduled job, and reads the number it produces.
What a stale window is
Section titled “What a stale window is”A stale window belongs to one citation, named by its page and entry id. It
opens at the first commit where cite check would have reported
source-changed, source-missing or source-never-true. It closes at the
commit that re-pinned the entry, restored the source, or removed the entry. A
moved pin is supported somewhere, so it opens no window.
Each first-parent commit of HEAD is read as it stood, with its own config,
entries and sources. So citations that moved from frontmatter into a sidecar
are no event, and a page renamed under git’s rename detection keeps its
windows.
Answer a reader’s bug about one page
Section titled “Answer a reader’s bug about one page”A reader filed a bug against a release, and the page it names cites the source. Run the walk on that page:
npx @hawkeyexl/manni cite history docs/src/content/docs/cite/set-up/index.mdxdocs/src/content/docs/cite/set-up/index.mdx error pages-from-collections src/cite/core/config.ts:308-313 stale 45 minutes v2.3.9 541b63e..bf8f986 source-changed, closed by a re-pin error collection-paths-config-relative src/cite/core/config.ts:308-313 stale 45 minutes v2.3.9 541b63e..bf8f986 source-changed, closed by a re-pin
2 stale windows on 2 citations in 1 page, none open. 1.5 stale pin-hours.1 release shipped a stale pin: v2.3.9.Walked 193 commits, 2026-09-11 to 2026-10-21.Each row names the severity, the entry and its source, how long the window lasted, and the releases inside it. The dim line under it names the opening and closing commits and why the window closed. Here one source change broke two pins, v2.3.9 shipped them, and a re-pin 45 minutes later closed both.
A release is a tag HEAD reaches whose commit contains the opening commit and
not the closing one. A tag on a merged side branch counts. A prerelease on a
branch that never merged does not.
Track the number from a scheduled job
Section titled “Track the number from a scheduled job”The pull request gate keeps new windows short. The number is worth tracking
where the gate is not on yet, or where a baseline
forgives known stale pins. A baselined pin is still stale, so history counts
it.
-
Create the workflow file. Add
.github/workflows/stale-citations.yml:.github/workflows/stale-citations.yml # Measure how long citations stood stale, once a week.name: Stale citationson:schedule:- cron: "0 6 * * 1"workflow_dispatch:jobs:history:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v7with:fetch-depth: 0 # the walk reads every commit- uses: actions/setup-node@v7with:node-version: 24- name: Walk the citation historyrun: npx --yes @hawkeyexl/manni@latest cite history -f json > cite-history.json- uses: actions/upload-artifact@v7with:name: cite-historypath: cite-history.json -
Keep the full history. A shallow clone is refused with exit
2, because the walk would start at the oldest commit the clone holds.fetch-depth: 0fetches every commit and every tag. -
Read the number.
summary.staleSecondsis the total time every window stood stale, open windows included.summary.opencounts the windows still open atHEAD, andsummary.releasesnames the tags that shipped one.Terminal window jq '.summary | {staleSeconds, open, releases}' cite-history.json
history never exits 1, so the job stays green whatever it finds. It is a
measure, and cite check remains the gate.
Bound the walk
Section titled “Bound the walk”A long history takes longer to walk. --newer-than 90d reads only the
commits within 90 days of HEAD’s committer time. A window already open at
the first commit read is marked bounded, because its true start is earlier.
npx @hawkeyexl/manni cite history --newer-than 90d -f jsonThe span counts back from HEAD, not from the clock. Two runs on one
checkout print the same report, and an open window runs to HEAD’s committer
time for the same reason.