Skip to content

fix(pr-artifacts): read GitLab uploads through the token-readable API - #1228

Open
yuensunn wants to merge 1 commit into
backnotprop:mainfrom
yuensunn:fix/gitlab-upload-artifacts
Open

fix(pr-artifacts): read GitLab uploads through the token-readable API#1228
yuensunn wants to merge 1 commit into
backnotprop:mainfrom
yuensunn:fix/gitlab-upload-artifacts

Conversation

@yuensunn

@yuensunn yuensunn commented Aug 7, 2026

Copy link
Copy Markdown

Problem

GitLab serves /uploads/<secret>/<file> from a Rails web route that only honors session cookies. The PRIVATE-TOKEN header that pr-artifact-document.ts already resolves via glab config get token is not accepted there, so every GitLab upload attachment referenced by an MR is unreadable.

Worse, the failure is silent. The sign-in hop answers HTTP 200 with an HTML login page, which then gets rendered as the artifact in the artifact pane.

Measured against a self-hosted GitLab 18.8.0-ee instance with a valid token (/api/v4/user → 200):

/uploads/<secret>/pack.md                        → 302 → /users/sign_in
/<group>/<repo>/uploads/<secret>/pack.md         → 302 → /users/sign_in
/-/project/<id>/uploads/<secret>/pack.md         → 404
/api/v4/projects/<id>/uploads/<secret>/pack.md   → 200  ✅

Changes

  1. Route GitLab uploads through the uploads REST API. gitlabUploadApiUrl() rewrites both the bare /uploads/... and project-scoped /<projectPath>/uploads/... forms to GET /projects/:id/uploads/:secret/:filename, wired into the existing providerContentUrl() alongside the /-/blob//-/raw/ rewrite. The allowlist and credential plumbing are unchanged — they were already correct.

  2. Stop following redirects into a provider sign-in page. Landing on /users/sign_in, /users/auth/..., /login, /session, or /oauth/authorize now throws PRArtifactDocumentError(401) with the matching gh auth login / glab auth login --hostname <host> hint. An auth gap becomes legible instead of surfacing as a corrupt artifact. This is worth having independently of change 1.

  3. Refine the opaque content type. The uploads API answers every file with application/octet-stream, and /api/pr-artifact-content serves the provider type with nosniff — without this, images and video would stop rendering. Falls back to an extension lookup only when the provider type is application/octet-stream.

Verification

  • bun test packages/shared/ — 761 pass, 0 fail (4 new tests)
  • tsc --noEmit -p packages/shared/tsconfig.json — clean
  • Full bun test — 2789 pass / 25 fail; unmodified main produces the identical 25 failures (Pi-extension and AI-runtime suites, unrelated to this change)
  • Live end-to-end fetch of a real MR upload on GitLab 18.8.0-ee: returns the actual file, contentType: text/markdown

Known consideration

GET /projects/:id/uploads/:secret/:filename was added in GitLab 17.4. On older self-hosted instances it 404s, which surfaces as a 502 — where today those uploads fail anyway (redirect to sign-in). Happy to add a version gate or a fallback to the web route if you'd prefer explicit handling.

Note on CI

The Test job failure on this branch is edit-mode Discard repaints the pristine diff (DOM) > pristine content is visible ... promptly after cancelEdit. It reproduces identically on upstream main (run 31138933804) and is unrelated to this change.

GitLab serves `/uploads/<secret>/<file>` from a Rails web route that only
honors session cookies. A `PRIVATE-TOKEN` request is redirected to the
sign-in page, so every GitLab upload attachment referenced by an MR was
unreadable — and because the sign-in page is HTML served with HTTP 200,
it was rendered as the artifact instead of the file.

Route upload links through `GET /projects/:id/uploads/:secret/:filename`,
which serves the same bytes for a personal access token. Both the bare
`/uploads/...` and project-scoped `/<path>/uploads/...` forms are rewritten.

Also stop following redirects into a provider sign-in path: fail with 401
and the matching `gh`/`glab auth login` hint so an auth gap is legible
instead of surfacing as a corrupt artifact.

The uploads API answers every file with `application/octet-stream`, and the
content route serves provider types with `nosniff`, so refine an opaque type
from the file extension to keep images and video rendering.

Verified end-to-end against a self-hosted GitLab 18.8.0-ee instance.
@yuensunn
yuensunn force-pushed the fix/gitlab-upload-artifacts branch from 508f00f to a05a185 Compare August 7, 2026 02:57
@yuensunn

yuensunn commented Aug 7, 2026

Copy link
Copy Markdown
Author

Before / after in the artifact pane

Reproduced against an MR whose description links a /uploads/<secret>/markdown-sample.md attachment, on a self-hosted GitLab with SSO. Same MR, same glab credentials, only the patch differs.

Before — the artifact pane renders the login page's HTML source instead of the file. The pane shows a <!DOCTYPE html> document with a hidden auto-submitting auth form, because the sign-in redirect answers HTTP 200 and that body is passed through as the artifact content.

Aftermarkdown-sample.md renders as markdown: headings, lists, block quotes, code samples, and the From PR description by @… footer.

Also confirmed at the API layer on a separate MR — GET /api/pr-artifact-document?url=<upload> on the local review server returns HTTP 200 and the real file content, where the same request on main returns the login page body.

Screenshots attached below.
Screenshot 2026-08-07 at 11 01 48 AM
Screenshot 2026-08-07 at 11 05 25 AM

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.

2 participants