A read-only GitHub dashboard that shows which pull requests need your attention,
and for how long — across your personal account and every organization you can
access. Four lists, oldest first, plus two history sections:
Ready to merge — PRs GitHub reports as mergeable now. An out-of-date branch
still counts, with a "Needs update" hint (a bot/manual update handles it) —
but only once its checks are green; while one is red or still running it
belongs under Stuck on checks, and no PR is ever in both lists.
Comments awaiting your reply — review comments where the last word isn't
yours: every unresolved inline thread on your own PRs, the threads you raised
on PRs you reviewed, and questions left in the body of a review rather than on
a line of the diff. Each row opens the comment itself, not the top of the PR.
Waiting on your review — PRs you're blocking others on.
Stuck on checks — your open PRs with failing/pending checks, or otherwise
blocked from merging (required checks, review, or conflicts).
Recently reviewed — open PRs you already reviewed, badged with the verdict
you left (Dismissed, struck through, when the author has cleared it), and
flagged "Updated since" when the author has pushed after it.
A PR that comes back for another review leaves this list and returns to
Waiting on your review.
Recently merged / closed — your own PRs that were merged or closed, newest
first, so finished work confirms itself.
The lower half of the board is two columns paired by subject: other people's PRs
on the left — the ones waiting on your review, then the ones you have already
reviewed — and your own on the right, blocked above finished. Each history sits
under the queue it is the history of, and the columns grow independently, so
expanding one never moves the other. Both histories start collapsed with a count
and expand to the latest 15, with Load more for the rest.
Unanswered review comments. An inline thread is waiting on you when it is
unresolved and its most recent comment isn't yours — replying adds a comment,
so your own last word means the ball is back in the reviewer's court. A review
body has neither replies nor a resolve bit, so it waits until you say
something on the PR after it; a reaction dismisses one, the same emoji that
dismisses a thread. Rows say which surface they came from, because the two are
answered in different places. The age counts from the comment, so you can see
what you've been sitting on for four days. Bots write most review comments, so
they're hidden behind a Show bot comments toggle.
Tracked checks → Awaiting. GitHub's API hides "expected" required checks
(e.g. a manually-triggered qa/smoke or automation) from non-admins. Name the
checks you care about — a default per owner (your personal account and every
org) plus per-repo overrides, with a
type-to-search repo picker — and PRison shows them as "⏳ Awaiting: <name>"
on a blocked PR until they report. Each check is added with a Required box
answered up front, and the board stops guessing: a required one is drawn solid and
holds the PR out of Ready to merge; an unticked one is shown for information,
drawn dashed, and blocks nothing — including when GitHub does report it, so a
red job that cannot hold the merge up stops reading like one that can. A check
you never named keeps the dashed chip, which is what "PRison doesn't know"
looks like.
Ignored checks. Some checks are broken rather than failing — the flaky
one, the one somebody else's team owns. Right-click (or click) its chip on the
board and choose Ignore this check. It stays on the card, muted instead of
red, but its result stops counting: the PR is no longer held out of Ready to
merge over it, it gets no bucket of its own under By check, and it is never
awaited. Everything ignored is listed in Settings → Ignored checks, under
the repo it was ignored on, where it can be renamed, widened to the whole
owner, or taken back. A conflict or a review gate still holds the PR —
ignoring a check says its result means nothing, never "merge it anyway" — but
GitHub's own blocked state doesn't, since a red required check is
exactly what puts a PR there.
History you can walk back into. Merged and closed-without-merging PRs, each
badged Merged or Closed with how long ago it ended — and next to them
the PRs you reviewed, so a discussion you left open is one click away instead
of lost in GitHub's notification list.
Summary tiles above the lists: how many PRs wait on your review, how many
comments wait on your reply, how many of yours are stuck on checks — each with
how long that queue's oldest one has been sitting, so every age says which list
it belongs to. The two where someone else is held up by you are coloured; the
rest stay quiet. They count the lists as filtered, so a
tile never disagrees with the list beneath it, and they cost no extra API
calls — it is all data already on the page. Ready to merge has no tile: it
is the one queue you want to be long.
A section index under the tiles: every section, in page order, with its
count — so nothing is more than a click from the top however long the board
gets, including the two histories at the foot of it. They are plain links, so
the Back button is the way back and keyboard focus travels with the view.
Everything folds by its header. Each of the four lists, each repo/check
group inside them, and both history sections collapse to a single line that
keeps its title and count — so a section you are not working in today stops
taking up the screen without ever hiding how much is in it. Folds last the
sitting, not forever: a board that quietly omitted your review queue tomorrow
morning would be worse than a long one.
Grouping — flat, by repository, or by check. Next to it in the filter
bar, a draft filter: All, Only drafts to see just what you have in
progress, or No drafts to get them out of the way.
One search box over the whole board. Type a word and every list narrows at
once — title, repository, PR number, author, comment text, file path, and the
names of the checks that are red on a PR. Terms are ANDed and each may land on
a different field, so acme 482 finds that one PR. It filters what is already
on the page rather than asking GitHub anything, it never touches what auto
refresh considers new, and it is forgotten when you close the tab.
Auto refresh (opt-in). Turn it on in Settings and pick how often to check
— every 5, 15, or 30 minutes, or hourly (30 minutes by default). PRison then
tells you what moved, not just what's new: a PR of yours that was approved
— you hear that as soon as it happens, not when CI eventually catches up and
the PR turns mergeable — a PR that became ready to merge, one that fell into
a merge conflict because somebody else merged first, checks that went
red, changes requested, a review asked of you, an author answering the review
you left with code, a fresh reply
on a thread, or one of your own PRs getting merged — while a PR simply falling
back to waiting (you pushed a fix) stays quiet. Works only while a PRison tab
is open — there's no background service.
Activity feed. Everything a poll detects is kept, newest first, behind the
bell in the header. The bell carries an unseen count and pulses while there is
something new (never against a reduced-motion preference), and each row names
the PR, says what happened, and links straight to it — a comment lands on the
thread itself. The last 100 events survive a reload. Opening the panel is what
marks it read, so the count waits for you instead of vanishing the moment you
return to the tab; the (3) PRison tab-title badge follows the same count.
Desktop notifications (opt-in). On the same schedule, changes that land
while you're on another tab also raise a desktop notification naming them —
acme/web #42 is ready to merge — up to three at a time, then +N more.
Settings shows whether your browser has actually granted permission and offers
a test notification, and says where else to look if nothing appears: your
operating system decides separately whether the browser may show anything, and
it refuses in silence. Whatever the notification misses is in the activity
feed regardless — and, if your operating system keeps swallowing them, in the
menu bar: extras/menubar/ puts an unread count there, where nothing has to
be permitted for you to see it and nothing disappears after five seconds.
Catch-up on open. PRison remembers what everything was doing when you
last closed it, so a review that arrived overnight is still news in the
morning — the first load reports what moved while no tab was polling,
instead of quietly accepting it as the way things always were. It fills the
feed and the bell, never a desktop notification: what it reports is by
definition old, and a browser reloads a tab on its own — a discarded one
restored in the background is not you opening PRison.
Last refreshed indicator. The filter bar says how long ago the data
landed, so you always know whether you're looking at something stale.
Shows what a refresh costs. GitHub prices every query, the allowance is
hourly, and it is shared with anything else signed in as you — so how often
you refresh is a spending decision. Settings → Auto refresh names the price
of the last refresh and what is left of the hour, and the board warns while
there is still enough left to act on rather than after the wall.
Says when GitHub's budget is spent. The API allowance is hourly and
account-wide, so a busy morning can empty it — and every list then fails at
once, which looks exactly like the app being broken. PRison tells the two
apart: a spent budget gets its own notice, with the clock time it comes back
and the setting that stops it happening again, instead of a retry that is
guaranteed to fail.
Partial-data notice. When GitHub drops part of a response (an org restricts
the token, a search times out), a banner says so instead of silently showing
less — with a Retry button, like every failed list.
Rides out GitHub's rate limiting. A refresh needs seven queries, and asking
for them all at once trips GitHub's secondary rate limit — which applies to
your whole account, so lists fail together and the page looks broken. PRison
sends three at a time instead. That costs nothing: the refresh is as fast as it
was, because its speed was always set by the slowest single query, and each
query actually comes back quicker once GitHub isn't throttling the burst.
Should the limit still land, and GitHub says how long the block runs and it's
short, PRison waits that out and asks once more instead of making you press
Retry yourself. When GitHub asks for longer than a refresh should take — or
doesn't say at all — PRison steps aside and shows the banner rather than
guessing, since coming back early only extends the block.
Eight themes, each with a light and a dark ground. A theme owns its accent,
its three status colours and its typefaces; the ground underneath is a separate
choice, so switching it reads as the light changing rather than the theme
changing. Default is the original pair; Aurora is built from four
atmospheric emission wavelengths, İznik from the metal oxides the 16th
century tile workshops fired, and Cyanotype from the 1842 blueprint
reaction and its negative. Coral takes a reef and the algae that colour
it, its two grounds a depth rather than a mood — at 30 m the sea has absorbed
the red end of the spectrum. Saffron takes four parts of one crocus,
Lichen four species off one rock face, wet and dry. Reflectivity is a
weather radar's dBZ scale, and the one accent here that isn't blue or green.
All sixteen palettes are measured against WCAG AA by a test, with nothing
exempt. Pick one in Settings, where each row previews itself — the swatch is
stamped with that theme, so it renders in the real palette rather than a copy
that can drift.
Responsive two-column layout, minute-level ages, colour-coded lists, and a
Refresh button.
Personal account + per-org filter in the top-right switcher, and a
single-repo filter beside it for the days spent inside one repository.
Both narrow the GitHub search itself rather than the rows already on screen,
so the 50-result window is spent on what you are looking at.
Your own access — sign in with the GitHub CLI or a token; no third-party
app to approve. Every row deep-links to GitHub; PRison never writes anything.
PRison runs on your own machine — no third-party app to approve. The easiest
way is Docker (one command); or run it locally with Node.
Sign Out ends your session — it clears the encrypted cookie. It cannot revoke
the host's credentials: on a GITHUB_TOKEN-configured instance, one click signs you
back in, and anyone who can reach the instance can do the same. That is what the
warning below is about.
Warning
Sign-in mints a session from the host's GitHub credentials (your `gh` CLI token
or a `GITHUB_TOKEN`). PRison is designed to run on your own machine — do NOT
expose a `gh`-authenticated or `GITHUB_TOKEN`-configured instance on a reachable
network without adding your own access control.
GITHUB_TOKEN="$(gh auth token)" docker compose up --build # http://localhost:3000
The prison-data volume holds the generated AUTH_SECRET, so your session
survives a restart. Images are published for linux/amd64 and linux/arm64.
Passing your gh token signs you in automatically — needed for SSO-restricted orgs
(where SSO/SAML enforcement blocks classic PATs). The token rotates, so re-run when
it expires. Without GITHUB_TOKEN, just open the app and paste a token.
The container is the dashboard; on a Mac this is the other half. A desktop
notification is only as reliable as the four switches between the page and
your screen, and it is gone in seconds whether or not you were looking.
extras/menubar/prison.1m.py is a SwiftBar (or xbar)
plugin that puts an unread count up there instead: it appears when
something lands and stays until you click it.
brew install --cask swiftbar
mkdir -p ~/SwiftBar # the folder SwiftBar asks for on first launch
cp extras/menubar/prison.1m.py ~/SwiftBar/
chmod +x ~/SwiftBar/prison.1m.py # the bit survives cp, not a raw download
It reads the PRison you just started — no token of its own, no configuration,
and no schedule of its own: it asks GitHub on the same interval the dashboard
uses, so the two never spend the hourly budget twice. Clicking an unread row
opens that PR and marks it read; the live totals sit one click below. See
extras/menubar/README.md.
npm run dev generates AUTH_SECRET into .env.local on first run (it encrypts
the session cookie) — nothing to configure. Open the app and click Sign in with
GitHub CLI; the server reads your CLI token and stores it only in an encrypted,
httpOnly cookie — never in the browser.
Select the read:org and repo scopes, generate, and copy it.
Note
For SAML SSO orgs, click **Configure SSO** on the token and **Authorize** it —
self-service, no org owner approval. Some orgs forbid classic PATs entirely;
there the GitHub CLI token is the only way in.
Scopes the board to All / your personal account / a single org.
Bell
Opens the activity feed and marks it read.
Sliders icon
Opens Settings (below).
Sun/moon
Switches between the current theme's two grounds, and names them — it reads "Switch to Aurora Night" rather than "Switch to dark theme".
Sign Out
Clears the stored token.
The Settings menu has six sections:
Comments — show bot comments, hide comments you reacted to.
Auto refresh — on/off, how often to check, and the notification
permission.
Tracked checks — add the checks to see as "Awaiting" one at a time, each
with its own Required box, and rename, re-mark, or remove any of them later.
Both check panels work one scope at a time: pick an owner or a repository from
the scope picker at the top and edit just that list, so the panel stays the
height of one list however many repos you configure.
Ignored checks — everything you wrote off from the board, under the repo
it was ignored on, plus an owner scope for a check that is broken everywhere.
A repo can also be picked here, to write a check off before the board has
ever drawn its chip.
Appearance — pick one of the eight themes, each previewing its own colours
and typeface.
About — version, repository, license, and Check for updates, asked
for on the spot. Tell me when a newer version is out turns it into a
standing question: the version in the header becomes a link to the release
notes once your container has fallen behind. Off by default; asks GitHub once
a day, without your token, and stays quiet when it can't reach it.
Groups the lists. Group headers fold and unfold, and under By repo the ↗ beside one opens that repository on GitHub.
All / Only drafts / No drafts
Filters by draft state.
Search
Narrows every list to the rows matching all your terms; Esc clears it.
Refresh
Re-fetches without reloading the page.
Updated Xm ago
How fresh the data is, next to Refresh.
Click a PR title (or a suggested-action link) to jump to GitHub — a comment
row lands on that exact thread. On a Mac, the same counts are in
the menu bar without opening the tab.