For the complete documentation index, see llms.txt. This page is also available as Markdown.

Upload media

Upload images and videos to Argos from the CLI, the Node.js SDK, the REST API or an AI agent, and get them into a pull request with a managed comment.

Every surface uses the same three-step flow described in How it works. The CLI is the shortest path; the API is there when you are not in a Node.js environment.

From the CLI

argos media upload before.png after.png

Pass as many files as you like. Each one is validated up front — an unsupported type, an unreadable path or a name collision fails the batch before anything is transferred — then uploaded in turn and printed with its own share URL and Markdown embed. Add --json when you parse the output.

Flag
What it does

--branch <branch>

Stage the media on a branch. Argos publishes it — and posts the pull request comment — by itself once a pull request opens for that branch.

--pr <number>

Publish the media to an existing pull request. Detected with the GitHub CLI when neither this nor --branch is given.

--no-pr

Skip that detection and upload the media unattached.

--state <before|after>

Which half of a before/after pair these files are. Inferred from a file name ending in -before or -after.

--description <text>

Prose shown under the media in the pull request comment.

--visibility <team|public>

Who can open the share page. Defaults to your project's visibility — public for a public project, team for a private one. See Share links.

--no-compress

Upload images exactly as they are instead of converting them to WebP.

--project <owner/project>

Project to upload to. Required with a personal access token; also ARGOS_PROJECT.

Media belongs to a project, so it inherits that project's access and moves with it when the project is transferred. In CI, media upload authenticates like every other Argos command: ARGOS_TOKEN, or tokenless GitHub Actions authentication with no token at all. See Tokens and permissions for the other commands.

Getting media into a pull request

An agent produces screenshots while it is still doing the work; the pull request comes after, if it comes at all. So a media attaches to a branch or to a pull request:

  • --branch <branch> — the media is staged. It is real from the start: shareable, listed, with its URL. When a pull request opens for that branch, Argos attaches everything staged there and posts its comment, unasked. Naming a branch needs no GitHub connection and no open pull request.

  • --pr <number> — the media is published to that pull request immediately.

A media keeps its branch after publishing, as a record of where it came from, so argos media list --branch <branch> finds everything uploaded for the work in hand across the moment the pull request opens.

When you pass neither

Argos asks the GitHub CLI for the pull request of the branch you are on:

Only when you pass neither flag. --pr is taken as given, and --branch is already an answer to the question — detecting a pull request over it would publish immediately and discard the staging you asked for.

Detection never fails an upload. gh not installed, not signed in, no pull request open for the branch yet, not a git repository at all — all mean the same thing, "nothing to attach to", and the media uploads with its share URL either way. Pass --no-pr to skip the lookup, for a screenshot that has nothing to do with the branch that happens to be checked out.

CI is the case where detection usually finds nothing: gh is often absent, and a checkout is frequently in a detached HEAD. Pass --pr or --branch explicitly in a workflow rather than relying on it.

Two boundaries: publishing needs the project connected to a GitHub repository with pull request comments enabled, and a pull request opened from a fork never claims a branch's staged media.

The managed comment

Argos maintains one comment per pull request — "Media uploaded by Argos" — listing every media published to it in a table, and edits it in place on each change. Attaching a media to a pull request and showing it there are the same act, not two. A before/after pair shares a single row, descriptions appear in a Notes column, and the table lists up to 20 media.

The comment is separate from the Argos build comment: standalone media has no build behind it, and a media upload never rewrites a status comment reviewers rely on. If a reviewer deletes the comment, Argos takes the hint and never recreates it on that pull request. Deleting a media removes its row immediately.

Before and after pairs

A file named checkout-before.png uploads as checkout.png labelled before — the suffix is lifted off the name, case-insensitively, and the extension stays. Uploading checkout-after.png alongside it gives the pair one identity, one row in the pull request comment, and a share page that compares the two side by side with synced pan and zoom.

--state before or --state after sets the label for files not named that way. It applies to every file in the invocation — passing it to an already-suffixed pair would make both files collide on one identity, which the CLI refuses rather than silently versioning:

A media's identity within its pull request (or on its branch) is its name. Re-uploading the same name adds a version rather than a second media: the id, the share token and therefore the URL are unchanged, and the share page — and any Markdown already pasted in a pull request — shows the newest upload. The version a reviewer commented on survives underneath, in the share page's history.

Re-uploading a file whose bytes haven't changed does nothing: Argos recognizes the content hash, skips the transfer, and adds no version.

While a media is staged, argos media update can rename it, edit its description, or move it to another branch (--no-branch detaches it, so no pull request will publish it). Once published, name and branch are fixed — the pull request comment and its review threads are built on them:

Image compression

The CLI converts PNG and JPEG images to WebP before uploading — a 252 KB PNG screenshot typically goes out at a tenth of the size, at a quality (85) where text and 1-pixel borders survive. The media's name keeps your file's extension: checkout.png stays checkout.png even when WebP bytes are sent, so a --no-compress re-run adds a version instead of creating a second media.

Compression is skipped whenever it would not help: videos, already-efficient WebP and AVIF, GIFs and animated PNGs, images past WebP's 16383-pixel dimension limit (a long full-page capture reaches it), and conversions that came out no smaller. A file that can't be converted is uploaded as-is rather than failing the upload. Pass --no-compress to upload every image untouched.

Converting applies a photo's EXIF orientation, then drops the rest of its metadata — including GPS coordinates. A file uploaded untouched keeps its metadata in the stored original; see What Argos does to your file.

Embedding the result

Copy the Markdown line the command prints. It is a picture wrapped in a link to the share page — [![name](fileUrl)](url) — so the media shows inline, and clicking it lands on the page where it can be compared, versioned and commented on.

Do not hand-write the embed from the share URL. url is an HTML page: ![name](url) renders as a broken image everywhere you paste it. The image part has to point at the file, which is what the Markdown line already does.

The picture is the file itself for an image, and the poster frame for a video. That difference is not cosmetic: GitHub renders an inline video player only for media it hosts itself, so a <video> tag or a bare .mp4 link pointing at Argos renders as a dead link. The poster-in-a-link shows something and plays when clicked.

The poster frame is derived by the CDN from the video itself, so it is available immediately — there is nothing to wait for and no second file to manage.

Uploading more than one file prints one extra block: the whole batch as a Markdown table, pairs side by side in one row, exactly as the managed pull request comment renders them. That is the thing to paste when you want all of them at once.

A public share link unfurls on its own. Argos serves OpenGraph and Twitter card tags with the page, and answers oEmbed at /oembed, so pasting one into Slack, Discord, Notion, Linear or a tweet shows the screenshot rather than a bare URL.

A team-only link deliberately does not: unfurl metadata is read by a crawler carrying no session, so anything in it would be public to whoever holds the link — including the file name. Team links stay opaque until somebody signs in. A public project's media is public by default, so its links already travel; from a private project, pass --visibility public on the upload when you mean one to.

Reading the feedback left on a media

An uploaded media has its own comment threads, on its share page. A comment can be pinned to a point on the image, so a reviewer can say "this button is misaligned" about a specific pixel rather than describing where to look.

That matters most for the agent that produced the screenshot. It cannot see the image, but it can read the coordinates. Find the media uploaded for the branch, then list its open threads:

Pinned coordinates are normalized to the image's own width and height, so 0.62,0.34 is 62% across and 34% down whatever size it is displayed at. A comment also records the version it was written against: a pin describes a spot on the bytes its author was looking at, so once the media has been re-uploaded, resolve that version with argos media versions 4821 and fetch that file rather than the newest.

Only open threads are listed — what comes back is what is left to do. Add --all to include threads already resolved.

Answer and close each thread as you deal with it:

argos media comment also covers get, edit, delete, unresolve, react, unreact, subscribe and unsubscribe, mirroring build comments. A new comment can carry its own pin with --anchor-point <x,y> (normalized 0–1); a reply inherits the spot its thread already points at. Every media comment command — reading included — needs a personal access token, because a comment has an author.

Resolve only what you actually fixed. A resolved thread drops out of the next media comment list, so resolving something you skipped is how feedback gets silently dropped.

Tokens and permissions

Command
Token

media upload, media update

Project token (ARGOS_TOKEN, or tokenless CI for upload), or a personal access token with review access.

media get, list, versions

Either token type, with access to the project.

media delete

Project token, or a personal access token with administrator rights on the project — a share URL may already be pasted somewhere, and deleting the media breaks it.

media comment …

Personal access token, reading included.

With a personal access token, media upload and media list need the project named: --project <owner/project> or ARGOS_PROJECT. A project token already identifies its project.

From the Node.js SDK

Options mirror the CLI: token, project, branch, prNumber, state, description, visibility and compress (true by default). It returns one media per file, uploaded sequentially in input order. See the Node.js SDK reference.

Pull request detection is the CLI's, not the SDK's — nothing here shells out to gh. Pass prNumber or branch yourself.

From the REST API

POST /media declares the file and returns the media plus an upload target:

state, description, visibility, branch and prNumber are optional. With a personal access token, add "project": "acme/web"; a project token already identifies its project.

POST the file to upload.url as multipart/form-data, appending every entry of upload.fields before the file part — the target stays valid for 30 minutes — then call POST /media/{mediaId}/finalize. Finalizing checks the file is what it claims to be, records an image's dimensions, bills the upload, and makes it reachable. When upload comes back null, Argos already holds this exact file and both steps are unnecessary.

The rest of the surface:

Endpoint
What it does

GET /media/{mediaId}

Read one media.

PATCH /media/{mediaId}

Edit name, description or branchstaged media only; null clears.

DELETE /media/{mediaId}

Delete the media, its versions and its comments. Project administrators only.

GET /media/{mediaId}/versions

The upload history, newest first — when versionCount says there is one.

GET /projects/{owner}/{project}/media

List, filtered by branch, prNumber, stage, search, type.

/media/{mediaId}/comments/…

The full comment surface — threads, reactions, resolution, subscriptions. Personal access token only.

See the API reference for the full schemas.

From an AI agent

The MCP server exposes every one of these endpoints as a tool automatically — createMedia, finalizeMedia, getMedia, updateMedia, deleteMedia, listMedia, listMediaVersions under the media:read and media:write OAuth scopes, and the media comment tools under comments:read and comments:write.

For agents with a shell, the argos-upload skill is the better path: it teaches when to attach media to a pull request, how to embed the result so it renders, and how to read back the feedback a human left on it. Install it with:

Last updated

Was this helpful?