GitHub App
GitHub App
The recommended setup: bot-posted comments and live PR/issue titles.
What it adds #
Installing the App is the recommended setup — it’s what makes the staged loop hands-off. Five things change:
- The bot posts all linked media automatically. The attachments comment is created and kept
current on the server — no local
ghauth, no comments under contributors’ own accounts. - File pages show titles for private repos. Public repos work out of the box; the App grants read access for private ones.
- Titles stay current. Webhooks pick up when a PR or issue is renamed, closed, or merged.
- Screenshots staged before the PR exists get promoted automatically. See Staging before a PR exists.
- Images dropped directly on GitHub get mirrored in. Attachments people drag into PR and issue text are imported into the workspace so they survive GitHub’s own attachment lifecycle. See Imported attachments.
Everything in these docs still works without it — see Without the App.
Install it #
Install the App on GitHub and pick the repositories you attach files to (add more later in GitHub’s settings). That’s the whole setup — the next command run against an installed repo posts as the bot instead of your own account:
uploads attach ./after.pngStaging before a PR exists #
No PR open yet? On a branch, a bare put stages against it by default — no flag needed. Staged files
are hosted and public immediately; there’s just no PR to comment on yet.
uploads put ./progress.pngA bare uploads screenshot stages the same way, carrying its derived path/url/env/viewport
metadata through promotion once the PR opens.
Staging several files at once? Use uploads attach --branch instead — see staging & binding
states
→.
The moment a PR opens for that branch (or reopens, or gets new commits), the App promotes the staged
files onto it and posts or updates the bot comment — no CLI involvement. Without the App, the next
uploads attach against that PR promotes them, or run uploads attach --promote with no files
(--no-promote skips it).
Promotion copies — it never deletes the staged original, so URLs you’ve already embedded keep working. Staged files stick around under the same rules as anything else in your workspace. The one caveat: a file staged more than 30 days before its PR opens won’t auto-promote (it’s still there, just not attached automatically).
Imported attachments #
When someone drags an image or video into a PR or issue on GitHub itself, or attaches one with
gh --attach, the App imports it into the workspace automatically. Imported files carry the same gh.* metadata as CLI uploads, so they show
up in search and on the “From GitHub” rail of file pages — and they survive even if GitHub’s own
attachment copy goes away. They never appear in the managed attachments comment; they’re an index,
not a source of truth.
Importing is on by default for linked repos. Turn it off per workspace on the workspace’s
comment settings page, or per repo with ingestGithubAttachments: false in
.uploads.yml.
Two filters keep low-value files out:
- Bot authors are skipped. An attachment in a body or comment authored by a
[bot]account — CI status GIFs, badge images, and the like — is not imported. Repos that genuinely want bot-posted media can re-admit it withingestBotAttachments: truein.uploads.yml. - Tiny images are skipped. An image under 200 pixels on either side (emoji, badges, tracking pixels) is not imported. The original still renders on GitHub either way.
Permissions & events #
The App asks for one permission: Issues and Pull requests, read & write — to read titles and
state and post the one managed attachments comment. It never touches other comments, PR
descriptions, or your code. If your org installed the App before a permission was added, GitHub holds
the upgrade until an org admin approves it. In the meantime, the CLI prints the approval link and
posts via gh.
The App also needs webhook event subscriptions (Settings → your App → Permissions & events → Subscribe to events):
- Required —
issuesandpull_request. Without these the ping still shows green, but title updates and staged-file auto-promotion silently do nothing. - Recommended —
issue_comment. Enables self-healing comments; the App works fully without it.
uploads github doctor checks the subscriptions directly and reports anything missing.
Branch-rename following for staged uploads (see
Attach & share) is done client-side by the CLI on purpose, so
the App doesn’t need Contents: read (“read access to code”); that can be revisited if more use
cases justify asking for it. The follow-up happens the next time uploads stages or promotes on the
renamed branch; if the PR opens before that, attach --from-branch <old> still recovers the files.
Repo bindings #
Each repo maps to at most one workspace’s managed comment. The binding is created implicitly the
first time a workspace comments, attaches, or promotes against a repo; inspect or claim it explicitly
with uploads github link --status.
- Bindings can’t be stolen. Claiming a bound repo just reports the current owner.
uploads github unlinkreleases your own binding; an operator can reassign stuck ones from the admin panel. - Other workspaces can’t post to your repo. Their
comment/attachcalls get anot_authorizeddecline with noghfallback — nobody can deface or duplicate the bot comment on your repo. - First claims require push access. Before a workspace can bind an unclaimed repo, the server verifies live against GitHub that the calling token’s linked GitHub account can push to it. Tokens with no linked GitHub identity can’t make first claims (they still work on repos already bound to them), and repos bound before this check existed keep working.
Self-healing comments #
If the managed comment is deleted or edited out from under the bot, the App restores it automatically
on the next comment activity in that PR or issue — same attachment list, no CLI call needed. This
requires the issue_comment event (see Permissions & events). A briefly missing
comment is expected and not a reason to repost one manually.
Duplicate marker comments — the occasional webhook-vs-CLI race that creates two — are collapsed the
same way: the next sync keeps the oldest, updates it, and deletes the extras. And uploads meta set
on a gh/…-keyed object refreshes the comment automatically whenever it changes path or state,
so backfilled metadata shows up without waiting on the next attach.
Without the App #
uploads attach posts the same managed comment through your local gh CLI, and file pages still
resolve titles for public repos. Uninstalling just returns you to that behavior — hosted files and
their URLs are unaffected either way.