Open pull requests and issues from GitHub and GitLab in your macOS menu bar and desktop widgets
Overview
Pull requests, merge requests and issues from GitHub and GitLab, in your Mac menu bar and on your desktop. Gitwall is a free, open-source macOS app for everyone who waits on code review or keeps an eye on a team's work. It gathers open pull requests (merge requests on GitLab) and issues from the repositories you choose, sorts them by latest activity and shows review, CI, draft and merge-conflict state at a glance. Click an item to open it in the browser. There is no server: your Mac talks to GitHub and GitLab directly. , unzip it and move Gitwall.app to Applications. The build is signed with a Developer ID and notarized by Apple, so it opens without a Gatekeeper warning. It does not update itself; the Mac App Store version does. Each account you add comes with three presets: pull requests assigned to you, issues assigned to you, and pull requests waiting for your review.
README
Gitwall
Pull requests, merge requests and issues from GitHub and GitLab, in your Mac menu bar and on your desktop.
Gitwall is a free, open-source macOS app for everyone who waits on code review or keeps an eye on a team’s work. It gathers open pull requests (merge requests on GitLab) and issues from the repositories you choose, sorts them by latest activity and shows review, CI, draft and merge-conflict state at a glance. Click an item to open it in the browser. There is no server: your Mac talks to GitHub and GitLab directly.
Download
Download the latest release, unzip it and move Gitwall.app to Applications. The build is signed with a Developer ID and notarized by Apple, so it opens without a Gatekeeper warning. It does not update itself; the Mac App Store version does.
Requires macOS 14 Sonoma or later.
Features
Presets for every account, out of the box. Each account you add comes with three presets: pull requests assigned to you, issues assigned to you, and pull requests waiting for your review. Change their filters, delete them or build your own from any mix of accounts, repositories, pull requests and issues.
Filters that work the same on both platforms. Relation to you (author, assignee, requested reviewer), drafts, review state, checks, merge conflicts, labels, author logins, age, milestone and text. Each preset also decides which events notify you.
Archived repositories never show up: nothing in them can be merged or closed, so Gitwall leaves them out of every list and does not offer them when you pick repositories.
Pull requests that a cloud agent opened for you stay in “Waiting for my review” even though they are drafts:
Copilot and the Claude and Codex GitHub apps request the review before a person marks the pull request ready.
Filter the agents themselves with author logins such as copilot-swe-agent or cursor.
A query field that speaks GitHub search syntax. When the tick boxes run out, every preset has a Query field. Gitwall reads it with GitHub’s own rules and checks it against the items it already has, so it works for GitLab too and the widget always agrees with the menu bar.
assignee:@me AND (assignee:franta-dxh OR assignee:lumir-sokol OR assignee:tomgilsky)
That one means assigned to me and to at least one of the three, and it finds the same items when pasted
into GitHub’s issue search. A space works as AND too, AND binds tighter than OR, parentheses group up to five
levels deep, and a leading - excludes a term. Gitwall also reads a comma inside a qualifier as OR
(assignee:franta-dxh,lumir-sokol), but GitHub documents the comma only for label: and ignores it for assignee:,
so prefer OR when the query should work in both places.
| Qualifier | Matches |
|---|---|
assignee: |
anyone assigned |
author: |
who opened it |
label: |
a label |
milestone: |
the milestone |
reviewed-by: |
someone who reviewed it |
review-requested: |
someone whose review was requested |
involves: |
author, assignee or reviewer |
repo: / org: |
acme/app / acme |
is: / type: |
pr, issue, draft, open |
| a bare word | the title, the repository or #number |
@me is the account you signed in with; assignee:@octocat and assignee:octocat mean the same. Put values
with spaces in quotes: milestone:"Q4 2026". A bare @lumir-sokol is an error, as on GitHub; write
assignee:lumir-sokol, or "@lumir-sokol" to search the text. There is no NOT and no minus in front of
parentheses. A query Gitwall cannot read matches nothing and says why, so a typo can never quietly widen a
preset.
Issues too, not just pull requests.
Desktop widgets in four sizes. Counter, List, Board and Wide Board. Add as many as you like; each widget shows the preset you pick for it.
Right there in your menu bar, with the number of reviews waiting for you next to the icon.
And also:
- Sign in with GitHub or GitLab in one click, or paste a personal access token. OAuth tokens refresh in the background, so you sign in once.
- GitHub.com, GitHub Enterprise Server, GitLab.com and self-managed GitLab (16 or newer), several accounts at once.
- Local notifications for new items, review requests, approvals, requested changes, failed checks, merges and closes.
- A heads-up a week before a personal access token expires.
- Private by design: no analytics, no third-party services, credentials only in the macOS Keychain. See the privacy policy.
Quick start
- Connect an account. The first launch opens a short walkthrough. Choose Sign in with GitHub (you confirm a code at github.com) or Sign in with GitLab, or paste a personal access token. The account’s three presets appear right away.
- Pick repositories. Tick single repositories, or a whole GitHub organization or GitLab group so new repositories are picked up automatically.
- Add a widget. Right-click the desktop › Edit Widgets… › search “Gitwall” › drag a size onto the desktop. Right-click the widget › Edit “Gitwall” › choose its preset.
Supported hosts
| Host | Sign in | Or paste a token |
|---|---|---|
| github.com | OAuth device flow, built in | Fine-grained or classic |
| GitHub Enterprise Server | Your own OAuth App’s client ID (Settings › Accounts › Advanced) | Classic, or fine-grained where your server offers them |
| gitlab.com | OAuth with PKCE, built in | Fine-grained or legacy |
| Self-managed GitLab 16+ | Your own application’s client ID, redirect gitwall://oauth/gitlab |
Legacy, or fine-grained on GitLab 19.2+ |
Personal access tokens
Signing in is the quickest way. A token is the way to go when your organization blocks OAuth apps, when your own server has no application for Gitwall, or when you want a credential that can only read. Paste it in Settings › Accounts › + › Use a personal access token instead. Gitwall keeps it in the macOS Keychain and warns you a week before it expires.
To move an account from a token to signing in, or back, use its menu in Settings › Accounts (Sign in with GitHub…, Use a Token Instead…). The account keeps its repositories, presets and widgets. Removing the account and adding it again does not: repositories and presets belong to the account, so a new one starts empty. Add Account notices when you are already connected and offers to replace the sign-in instead.
| Fine-grained token | Classic (GitHub) or legacy (GitLab) token | |
|---|---|---|
| GitHub | Read-only. One token covers one owner: your account or one organization. | Scopes repo and read:org. One token covers all your organizations, but repo also allows writing. |
| GitLab | Read-only, GitLab 19.2 or newer, but without each reviewer’s review state. Needed where a group or the instance enforces fine-grained tokens. | Scope read_api, GitLab 16 or newer. |
GitHub fine-grained token
GitHub classic token
GitLab fine-grained token
GitLab legacy token
Development
You need Xcode 26 and XcodeGen.
brew install xcodegen
git clone https://github.com/prokopsimek/gitwall.git && cd gitwall
make run # generates the project, builds Debug and launches it
| Command | What it does |
|---|---|
make run |
Debug build and launch |
make test |
swift test for every package, then the app tests |
make test-packages |
Package tests only (fast, no Xcode UI) |
make install |
Release build into ~/Applications, for daily use |
make screenshots |
App Store and README screenshots from built-in demo data |
make archive / make release |
App Store archive / notarized download (see RELEASING) |
project.yml is the source of truth; the generated Gitwall.xcodeproj is not committed.
How it fits together
flowchart LR
subgraph App["Gitwall.app (menu bar, sandboxed)"]
UI["Menu bar, main window,settings, onboarding"]
Sync["SyncEngine"]
Tokens["Keychain tokens+ TokenRefresher"]
end
GH["GitHubProviderGraphQL + REST"]
GL["GitLabProviderGraphQL + REST"]
Store[("App Groupconfig.json · snapshot.json")]
Widget["GitwallWidget(never touches the network)"]
UI --> Sync
Tokens --> Sync
Sync --> GH & GL
GH --> API1(("GitHub API"))
GL --> API2(("GitLab API"))
Sync --> Store
Store --> Widget
Store --> UI
The app fetches and writes a snapshot into the App Group; widgets only read it and apply their preset with the
same FilterEngine the app uses, so both always agree.
| Where | What lives there |
|---|---|
Packages/ |
All logic, test-first: models, filters, sync, providers, OAuth |
Gitwall/ |
App target: windows, menu bar, settings, onboarding |
GitwallWidget/ |
The four widgets (and why their kinds must never be renamed) |
Scripts/ |
App Store Connect client, screenshot pipeline, icon rendering |
docs/adr/ |
Why things are the way they are |
Handy launch arguments (Debug builds)
| Argument | Use it for |
|---|---|
--debug-fresh |
A throwaway container and in-memory Keychain: try onboarding or sign-in without touching your real accounts |
--debug-demo |
Fictional accounts and items, no network; add --debug-demo-widgets to see all widget sizes |
--debug-onboarding-step |
Jump straight to one walkthrough screen |
--debug-github-token --debug-repos a/b |
Create a GitHub account without clicking |
build/DerivedData/Build/Products/Debug/Gitwall.app/Contents/MacOS/Gitwall --debug-fresh
Tests
Logic is developed test-first with Swift Testing, against fixtures captured from the real APIs. Tests that need a live server or a human are skipped unless you opt in:
GITWALL_GITHUB_TOKEN=github_pat_… GITWALL_GITHUB_REPO=owner/private-repo \
swift test --package-path Packages/GitwallGitHub --filter Integration
GITWALL_GITLAB_TOKEN=glpat-… GITWALL_GITLAB_URL=https://gitlab.example.com \
swift test --package-path Packages/GitwallGitLab --filter Integration
GITWALL_OAUTH_INTERACTIVE=1 swift test --package-path Packages/GitwallAuth --filter Interactive
Run the provider suites with both a classic and a fine-grained token when you touch the queries: fine-grained tokens only reach what their permissions list, so a new field can work with one and fail with the other.
Contributing
Issues and pull requests are welcome. A few house rules, spelled out in AGENTS.md:
- Logic goes into a package with a test first; the app and widget targets stay thin.
- Provider-specific types never reach the UI; add a capability instead of
if kind == .github. - Never commit tokens or keys. OAuth client IDs in
OAuthClients.swiftare public by design. - Commit messages follow Conventional Commits.
License
MIT, see LICENSE. Made by Prokop Simek.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add prokopsimek/gitwall