Skip to content

fix(github): load PR uploads from private repositories - #23

Merged
tebiggs merged 6 commits into
mainfrom
fix/mobile-private-pr-attachments
Nov 8, 2026
Merged

tebiggs merged 6 commits into
mainfrom
fix/mobile-private-pr-attachments

Conversation

@tebiggs

@tebiggs tebiggs commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Image attachments in a private repository's pull request still show as blank tiles on Android and as broken images on web. Upstream's fix, now on main (pingdotgg#21631, pingdotgg#21632), renders a signed URL through POST /markdown, but only when the upload URL answers with an SSO sign-in page. Without SSO, GitHub answers a token's request for a private repository's upload with a plain 404 text/plain, so main passes the 404 through.

The fix

After a 404 for an upload whose repository is known, the GitHub media fetch reads GET /repos/{owner}/{name}. It renders the signed URL only when the repository is private: true. Upstream's render, signed-URL cache and checks then run unchanged.

  • The visibility is cached per repository for an hour. A failed lookup is not cached and counts as not private.
  • Public repositories: an upload redirects straight to the file, so nothing changes and no lookup is made. A file that is gone costs one cached lookup and keeps its 404.
  • No fallback where none can work: a repository the account can't see, no signed-in account, no repository, or a URL that isn't an upload keeps its 404 with no render. The SSO path is unchanged.

This PR first carried its own version of the repository plumbing and render. Main now has upstream's equivalent, so the branch takes main's version of every file (merge b1925ae) and adds only the 404 case in gitHubMediaFetch.ts.

Verification

  • With the server's own request shape (Node fetch, the token, no Accept), a private repository's upload answers 404 text/plain. With a browser-style Accept it answers 404 text/html, but the server doesn't forward Accept.
  • GET /repos/<private repository> returns private: true, and pingdotgg/t3code returns private: false.
  • renders an upload in a private repository… failed on main (expected 200, got 404) and passes now. leaves a 404 alone unless the repository is known to be private covers public, unreadable, and no-repository requests.
  • The gitHubMediaFetch, driver and AssetAccess tests pass, as do lint, format, knip and the source-control-github typecheck.
  • Not rerun live since the rebuild. The earlier live run (17 attachments, 200 image/png) used this branch's previous version of the same render: https://artifacts.ghostsignal.nexus/k/t3code-private-pr-attachments-web
  • The APK at 05fa731 still works for the phone check, because main's contract keeps the same repository field the APK sends. It needs a server running this branch: https://artifacts.ghostsignal.nexus/k/t3code-private-pr-attachments

Done with Claude Opus 5.5 in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

tebiggs and others added 2 commits November 1, 2026 19:45
A private repository's `github.com/user-attachments/assets/<id>` URL is a 404
to any token, so the media fetch came back empty and clients drew blank tiles.
When that URL is a 404, ask GitHub to render it in the change request's
repository, which yields a short-lived signed link, and fetch that instead.
Mobile and web now send the repository with the media resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tebiggs

tebiggs commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Merged fork main (d6ef174) into the branch at 09fb563. The merge had no conflicts.

  • The media fetch, definition, driver, AssetAccess, mobile media and web PullRequestMarkdown browser tests pass.
  • Typecheck passes for contracts, source-control-core, source-control-github, server, mobile and web.
  • GitHub still answers 404 to the attachment URL with a token, and POST /markdown still returns a signed link that loads as 200 image/png.

The APK linked in the description is from before the merge (05fa731).

🤖 Generated with Claude Code

The signed-link cache was keyed only by attachment and repository, so after
switching GitHub accounts or signing out, a link signed for the previous
account was still served for up to a minute. Key it by the credential's
fingerprint and render with that credential pinned, the way the PR API keys
its caches, and render nothing without a credential.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tebiggs

tebiggs commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Review fix, 23c7fdf

A cross-provider review (gpt-6.1-sol) found that the signed-link cache was keyed only by attachment and repository. After an account switch or sign-out, a link GitHub had signed for the previous credential could be served for up to a minute.

The cache key now includes the credential fingerprint, the same way GitHubPullRequestApi keys its caches. /markdown renders with that credential pinned, and with no credential nothing is rendered and the attachment stays a 404.

  • gitHubMediaFetch.test.ts: the private-attachment test now switches accounts (renders again, pinned to the new fingerprint) and then signs out (404, no render). It failed on the old code and passes now.
  • gitHubMediaFetch.test.ts + driver.test.ts (6 tests), lint, format, knip and the source-control-github typecheck pass.

Not rerun on a live server or device since the merge; the earlier live check went through the same gh credential this now pins.

🤖 Generated with Claude Code

tebiggs and others added 2 commits November 8, 2026 02:46
…-attachments

Upstream landed its own repository plumbing and signed-link rendering for
GitHub media (pingdotgg#21631, pingdotgg#21632), so this branch takes main's version of every
file it had changed. The private-repository fallback follows on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub answers a token's request for an upload in a private repository with
a plain 404, so only an SSO sign-in page led to a rendered signed URL and
private repositories showed blank attachments. After a 404, look up the
repository's visibility (cached for an hour) and render the signed URL only
when it is private. Public repositories, ones the account cannot see, and
requests without a repository keep their 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tebiggs tebiggs changed the title fix(server): private repository attachments load in pull requests fix(github): load PR uploads from private repositories Nov 8, 2026
An account that cannot read a private repository reused another account's
cached "private" answer and rendered its uploads anyway. Key the visibility
cache by the credential fingerprint as well, so that account looks the
repository up itself and keeps its 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tebiggs

tebiggs commented Nov 8, 2026

Copy link
Copy Markdown
Owner Author

Review: gpt-6.1-sol (Codex, high effort) approves 381f9aa

ID Round Blocking Finding Disposition
S1 1 yes The signed-link cache was shared across GitHub accounts Superseded: main's signed-URL cache keys by credential fingerprint. Sol verified it resolved in round 2.
S2 2 yes The visibility cache was keyed by repository alone, so an account that can't read the repository reused another account's private: true and rendered Fixed in 381f9aa: the cache key is { fingerprint, repository }, and the fallback is skipped when no one is signed in. The test fails first (200 instead of 404). Sol verified it in round 3.

No blockers are open. The gitHubMediaFetch, driver and AssetAccess tests pass on 381f9aa, as do lint, format and the source-control-github typecheck. Nothing has been checked live against GitHub or on a device since the rebuild.

🤖 Generated with Claude Code

@tebiggs
tebiggs merged commit 321332c into main Nov 8, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant