Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

unique-doc-ids-action

Fails a build when two documents in a directory claim the same id — SPEC-031, ADR-007, RFC-012.

The bug it catches

The id is picked when the document is written, on a branch, and the rest of the filename varies (a date, a slug). So two branches each take the next free id they can see, and the files do not collide on disk when both merge. Only the id collides — and the id is what people, agents, tracker tasks and code comments actually cite. Nothing notices.

It is not rare. Across four Full Stack House repos at the time this action was written: 7 collided ids in one, 6 in another, 5 in a third, 2 in a fourth. One repo had renumbered by hand three times and had five live collisions again; another gave up and wrote "left as-is — renumbering would break inbound links" into its README.

Usage

jobs:
  spec-numbers:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - uses: fullstackhouse/unique-doc-ids-action@v1
        with:
          dir: docs/specs

Run it locally too — the logic is a dependency-free bash script:

./scripts/check.sh --dir docs/specs

Inputs

Input Default Notes
dir docs/specs Also .ai/specs in some repos
prefix SPEC- ADR-, RFC-, …
id-width 3 Exact digit count; see below
ignore SPEC-000-template.md Space-separated globs
known-duplicates Ids already colliding on the base branch
check-open-prs off off | warning | error
github-token github.token Only for check-open-prs

id-width is fixed rather than "one or more digits" on purpose: with a variable width, SPEC-31-x.md parses as id 31, is never compared against SPEC-031-x.md, and a typo quietly buys itself a private id. Off-width filenames fail as malformed instead.

known-duplicates lets the guard land green on a repo that already has debt, while still failing on every new collision. The list only shrinks: once an id is no longer duplicated, leaving it listed fails the build on the base branch, because a stale entry silently re-permits the next clash on that id.

Where to run it

Run it on push to the base branch, not only on pull_request. A pull_request build checks out refs/pull/N/merge, which GitHub computes lazily — on a live PR we measured it three weeks behind the real base. A PR can therefore be green against a base that has since gained a colliding id, merge, and land the collision silently. The push run is what catches those.

Do not run it behind a paths / dorny/paths-filter gate. Filters usually exclude docs/** and **/*.md, and a docs-only PR is exactly how a duplicate id arrives.

Route the base-branch failure somewhere a human reads. This is the step that is easiest to skip and costs the most. When the guard fails on push, the offending PR has already merged and its author has moved on — the only signals are a red mark on a commit nobody revisits and whatever mail GitHub sends the person who pressed Merge. Detection nobody is told about is not detection, and a collision is cheap to fix the hour it lands and expensive a month later. Add whatever your repo already uses for CI alerts as a final step in the same job:

      - name: Notify on failure
        if: failure() && github.ref_name == 'main'
        uses: fullstackhouse/slack-notify-action@v1   # or your own alerting step
        with:
          bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
          channel: "#your-alerts"
          title: "Duplicate spec number on main"
          message: "Two documents under `docs/specs/` claim the same id. Renumber now, while almost nothing cites it yet."
          status: failure

Guard it on the base branch: on a pull_request the red check already sits on a PR its author is looking at, so alerting there is noise.

Make it a required status check, with "require branches to be up to date" on. Without that setting the merge ref is never recomputed and the race above stays open; with it, GitHub serializes the merges for you and the guard becomes a real gate rather than an alarm. A merge queue does the same without the rebase tax.

check-open-prs

Off by default, and deliberately so: an open PR may never merge, so an id it holds is not really taken, and enforcing it would burn ids on abandoned branches. What it buys is timing — it fires before the id reaches the document body, the PR description, the tracker task and forty code comments, which is the only moment the fix is free. Every other layer fires after that. warning is the useful setting.

Licence

MIT © Full Stack House

About

Fail when two documents in a directory claim the same id (SPEC-031, ADR-007, …)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages