Two separate problems around getUploadToken, both confirmed by testing.
1. References to write access imply more access than is actually required
gh image tells users they need write access, in the error message and in the docs. That is not true.
Verified by uploading to a public repo the account has read-only access to:
permissions: {"admin":false,"maintain":false,"push":false,"triage":false,"pull":true}
$ gh image banner.png --repo <owner>/<repo>

The upload completed end to end — token, policy, S3, finalize. pull access was sufficient. Triage
sits above read, so it is very likely fine too (untested).
This matches how the web UI behaves: any user who can comment on a public repo can drag an image into
a comment. Requiring write access was never GitHub's rule.
Affected:
internal/upload/token.go — uploadToken not found on repo page — do you have write access to %s/%s?
README.md — "uploadToken is only issued to users with write access on the target repository."
Both should stop asserting a write-access requirement.
2. The "Sign in to GitHub" case is not caught and falls into the wrong error handler
When the user_session cookie is invalid or expired, GitHub does not redirect and does not return
an error status. It serves HTTP 200 with a sign-in page that has no uploadToken in it.
| Cookie state |
HTTP |
uploadToken |
Page served |
| none (anonymous) |
200 |
present |
real repo page (~541 KB) |
user_session= (empty) |
200 |
present |
treated as anonymous |
| invalid / garbage |
200 |
absent |
<title>Sign in to GitHub · GitHub</title> (~46 KB) |
Reproduce:
curl -s -L -H 'Cookie: user_session=deadbeefdeadbeefdeadbeef' https://github.com/<owner>/<repo> \
| grep -oE '<title>[^<]*</title>'
# <title>Sign in to GitHub · GitHub</title>
Control flow today: status is 200 so we proceed, the regex finds no token, isSAMLProtected returns
false (correctly — this is not the org SSO interstitial), and we fall through to the write-access
error. So the most common real failure mode, an expired session, is reported as a permissions problem.
Users are sent to check repo access when the actual fix is re-extracting the token.
This has been observed in the wild in a public repo's CI, where a stale GH_SESSION_TOKEN secret
surfaced as do you have write access to …?.
Suggested fix: a third branch alongside isSAMLProtected, keyed on the sign-in interstitial (the
Sign in to GitHub title and absent "currentUser"), returning something actionable — e.g. "your
session token is invalid or expired — re-run gh image extract-token." Structurally the same shape as
the existing SAML detection: a 200 response that is not the real repo page.
Also worth correcting while in the docs: README.md says visibility "inherits from the target
repository, so a private-repo upload requires authentication to view." An unreferenced upload to a
public repo also 404s for anyone but the uploader until it is referenced in rendered content, which
the current wording implies is not the case.
Two separate problems around
getUploadToken, both confirmed by testing.1. References to write access imply more access than is actually required
gh imagetells users they need write access, in the error message and in the docs. That is not true.Verified by uploading to a public repo the account has read-only access to:
The upload completed end to end — token, policy, S3, finalize.
pullaccess was sufficient. Triagesits above read, so it is very likely fine too (untested).
This matches how the web UI behaves: any user who can comment on a public repo can drag an image into
a comment. Requiring write access was never GitHub's rule.
Affected:
internal/upload/token.go—uploadToken not found on repo page — do you have write access to %s/%s?README.md— "uploadTokenis only issued to users with write access on the target repository."Both should stop asserting a write-access requirement.
2. The "Sign in to GitHub" case is not caught and falls into the wrong error handler
When the
user_sessioncookie is invalid or expired, GitHub does not redirect and does not returnan error status. It serves HTTP 200 with a sign-in page that has no
uploadTokenin it.uploadTokenuser_session=(empty)<title>Sign in to GitHub · GitHub</title>(~46 KB)Reproduce:
Control flow today: status is 200 so we proceed, the regex finds no token,
isSAMLProtectedreturnsfalse (correctly — this is not the org SSO interstitial), and we fall through to the write-access
error. So the most common real failure mode, an expired session, is reported as a permissions problem.
Users are sent to check repo access when the actual fix is re-extracting the token.
This has been observed in the wild in a public repo's CI, where a stale
GH_SESSION_TOKENsecretsurfaced as
do you have write access to …?.Suggested fix: a third branch alongside
isSAMLProtected, keyed on the sign-in interstitial (theSign in to GitHubtitle and absent"currentUser"), returning something actionable — e.g. "yoursession token is invalid or expired — re-run
gh image extract-token." Structurally the same shape asthe existing SAML detection: a 200 response that is not the real repo page.
Also worth correcting while in the docs:
README.mdsays visibility "inherits from the targetrepository, so a private-repo upload requires authentication to view." An unreferenced upload to a
public repo also 404s for anyone but the uploader until it is referenced in rendered content, which
the current wording implies is not the case.