feat(media): media upload command, SDK and agent skill - #358
Open
gregberge wants to merge 8 commits into
Open
Conversation
- `uploadMedia()` in `@argos-ci/core`: registers each file, posts the bytes straight to storage with the signed policy, and finalizes. Sequential rather than concurrent — these are large files, and saturating an uplink makes every one of them slower. - `argos media upload|list|get|delete`. `upload` is a new subcommand rather than a change to `argos upload`, which already means "upload screenshots and create a build". - `argos-upload` skill: teaches when to attach media to a pull request, and why the printed Markdown has to be pasted verbatim — a video embeds its poster frame wrapped in a link, because GitHub renders inline players only for media it hosts itself. - Regenerated the API client types from the OpenAPI document. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The other half of uploading a screenshot: a human pins a comment to a spot on it,
and the agent that produced it has to be able to read that back.
`argos media feedback` is the one an agent reaches for — every open thread on a
project's media in one call, each with the file URL to go and look at and the
normalized coordinates of its pin. Open threads only by default, so what comes
back is what is left to do. `argos media comment …` covers the eleven single-
comment actions, mirroring `argos comment` for builds.
Two things were stale and would have failed at runtime:
- The generated API client predated the project re-scoping, so it still described
`/accounts/{accountSlug}/media` and knew nothing of the comment routes.
Regenerated; the only route it lost is the one that no longer exists.
- `media upload` and `media list` sent an account where the API now expects a
project, so an upload with a personal access token could not resolve a target
at all. Both now take `--project <owner/project>`.
`formatComment` only printed an anchor when a screenshot diff sat behind it, so a
media pin was invisible in the text output — the coordinates never reached the
reader they were written for.
Generated from the backend's own OpenAPI document rather than by hand, so the client describes what argos-ci/argos#2445 actually serves. Media is versioned now, and the shape moved with it: `slug` and the `comment` flag are gone, `state`, `description`, `branch` and `stage` arrived, media lives under a project, and the project-wide feedback endpoint was dropped in favour of listing a media's comments. `MediaVersion` and `PATCH /media/{mediaId}` are new. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CLI was written against a media API that has since changed under it, and three of its flags no longer existed on the other end. - `--branch` is the one to reach for while working: the media is staged, and Argos publishes it and posts the pull request comment itself once a pull request opens for that branch. `--pr` publishes straight away, and `--comment` is gone — attaching a media to a pull request and showing it there were never two decisions. - `--slug` is gone too. Identity is the name, so re-uploading `checkout.png` adds a version and the share URL already posted to a pull request shows the new image. A file named `checkout-before.png` uploads as `checkout.png` labelled `before`, which is what lets the two halves of a pair be compared side by side; `--state` sets it for files not named that way, and a batch where two files would collide on one identity is refused rather than silently versioned. - Images are converted to WebP before upload — a 252 KB PNG screenshot goes out at 25 KB. The media keeps the name it was given, because letting it follow the upload format would turn a re-run with `--no-compress` into a second media instead of a second version. Conversion is skipped when it would not help: a video, an already-efficient format, an animated GIF, an image past WebP's 16383px limit, or bytes that came out no smaller. `media update` and `media versions` cover the two endpoints that had no command. `media feedback` is gone with the endpoint behind it — a review is now read as `media list --branch` then `media comment list`, and a comment carries the version it was pinned on, which is what `media versions` resolves. `media upload` no longer goes through the build configuration when a token is already in hand. It refused to run outside a git repository, complaining about a missing commit that uploading a screenshot has nothing to do with, and rejected the token `argos login` stores because it is not 40 characters. Tokenless CI auth still takes the old path, where a branch and a commit always exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regenerated from the backend's OpenAPI document. `retentionDays` is gone from the `POST /media` body — argos-ci/argos#2445 fixed retention to the plan rather than taking it from the request — and the client stopped sending it in the commit before this one. Advertising a field the server now silently strips was the last thing left. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r compression Five things the upload path got wrong, found reviewing it. `resolveAuth` went *around* the configuration rather than through it, because `readConfig` insists on a branch and a commit — which uploading a screenshot has nothing to do with — and because `mustBeArgosToken` demands exactly 40 characters, which an `argos login` token (`argos_oat_` plus 40) is not. Both are now fixed at the layer that owns them: `readConfig` takes `requireGitContext`, and the token format knows the OAuth family. So media resolves its token, API and project the same way every other command does, and a malformed `ARGOS_API_BASE_URL` fails with "Invalid Argos API base URL" again instead of an opaque `TypeError` from fetch. That also closes a leak. The old fallback handed the whole parameter bag to the config layer, where `branch` means the branch of a *build* — and the CI tokenless exchange authorizes against it. `argos media upload --branch feat/x` from a job running on `main` asked to be authorized for `feat/x`. Only the three fields being asked about are passed now. Compression is an optimization, so it no longer refuses uploads: - Any failure — a file sharp declines to decode, an unwritable `$TMPDIR` — falls back to the original bytes, which is what the docstring always claimed. Before, a PNG that GitHub renders fine could fail a command that used to work. - The temporary file is deleted once the bytes are up, and its descriptor is never opened. `promisify(tmp.file)` drops both the fd and the remove callback, and `tmp` only sweeps at exit if asked to, so a few hundred screenshots meant a few hundred held descriptors and a few hundred WebP files left behind for good. - An animated PNG is left alone. Only the first frame would have survived, and the "is it smaller?" check cannot notice: one frame always is. libvips reports no page count for APNG, so the `acTL` chunk is read off the bytes. - Pixels are capped at the 80 Mpx the screenshot path already uses. The per-side WebP limit is not a bound on work — 16383x16383 clears it and is a gigabyte of raw RGBA, enough to take a CI container down over one upload. And two smaller ones: a batch now checks every file is readable before creating any media, instead of discovering a typo in file three with the first two already uploaded and billed; and the collision guard's key is written with an escape, because the literal NUL byte it held made the whole file binary to grep and to GitHub's diff view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`argos media comment list` is how an agent reads the feedback a human left on a screenshot, and it was answering with neither of the two things that feedback consists of. The list rendering printed author and text only, so the pin — the whole content of "this button is misaligned", the part that says *which* button — was visible in `--json` and nowhere else, along with the version the pin was placed on. Both are printed now, for build and test comments too. It also listed settled threads alongside open ones. `media feedback` used to send `resolved=false` by default and the endpoint that replaced it takes no filter, so the default moved into the CLI: open threads only, replies to a resolved thread included in what is dropped, and `--all` to see the rest. An agent asked to act on a review was otherwise being handed work somebody had already done. Two smaller ones: `Version: 3 of 2` was reachable, because `version` counts every version created while `versionCount` counts only the ones whose bytes landed — it reads `Version: 3 (2 uploaded)` now. And `media update --name ""` was PATCHed verbatim for the server to reject; an empty value means "clear this" everywhere else in that command, so it is refused locally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gregberge
marked this pull request as ready for review
August 10, 2026 11:35
`packages/cli/e2e` uploads the whole of the repo's `__fixtures__` to Argos as a visual test, so the animated PNG added for the compression test arrived as a snapshot — a 2x2 image nobody has any reason to review. It lives in the core package now, next to the only test that reads it. The next e2e build reports `media/animated.png` as removed; approving that once settles it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jsfez
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Client side of standalone media upload. Pairs with argos-ci/argos#2445, which adds the API.
Draft — iterating.
What's here
uploadMedia()in@argos-ci/core— registers each file, posts the bytes straight to storage with the signed policy, then finalizes. Sequential rather than concurrent: these are large files, and saturating an uplink makes every one of them slower while making progress output useless.argos media upload|list|get|update|delete|versions, plusargos media comment …— a new subcommand group, not a change toargos upload, which already means "upload screenshots and create a build". Every media endpoint the API exposes has a command;finalizeis not one of them, becauseuploaddoes it.--no-compressuploads the file untouched.argos-uploadskill — the actual distribution channel for agents. Teaches when to attach media to a pull request (and when text is better), how staged media reaches a reviewer, and why the printed Markdown has to be pasted verbatim: a video embeds its poster frame wrapped in a link, because GitHub renders inline players only for media it hosts itself. Hand-writing a<video>tag produces a dead link.skills/argos-cli— command map and auth table updated so they stay accurate.packages/api-client/src/schema.ts— regenerated from the API's own OpenAPI document rather than by hand.Following the API
The API moved while this branch was open, and three of its flags no longer existed on the other end.
--branchor--pr, and--commentis gone. Attaching a media to a pull request and showing it there were never two decisions.--branchis the one to reach for while working: the media is staged, and Argos publishes it and posts the comment itself once a pull request opens for that branch.--slugis gone. Identity is the name, so re-uploadingcheckout.pngadds a version and the share URL already posted to a pull request shows the new image, with nothing to edit.--state, and before/after pairs. A file namedcheckout-before.pnguploads ascheckout.pnglabelledbefore, which is what lets the two halves be compared side by side.--statesets it for files not named that way, and a batch where two files would collide on one identity is refused rather than silently versioned.--retentionis gone — the plan decides retention.media updateandmedia versionscover the two endpoints that had no command.media feedbackis gone with the endpoint behind it: a review is read asmedia list --branchthenmedia comment list, and a comment carries the version it was pinned on, which is whatmedia versionsresolves.Two things worth a look
checkout.pngeven when WebP bytes are sent). Name + state + branch is the server-side identity, so letting the name follow the upload format would turn a--no-compressre-run — or a file the compressor skipped — into a second media instead of a second version. The storage key's extension comes from the content type anyway.media uploadno longer goes through the build configuration when a token is already in hand. It refused to run outside a git repository, complaining about a missing commit that uploading a screenshot has nothing to do with, and rejected the tokenargos loginstores because it is not 40 characters. Tokenless CI auth still takes the old path, where a branch and a commit always exist.Type of changes
enhancementChecklist
Notes
media uploadandmedia updatetake a project token or a PAT with review access;media get,listandversionstake either with access to the project;media deleteneeds project admin; everymedia commentcommand needs a PAT, reading included.upload-oidce2e test fails locally on a clean tree too — it needs a real GitHub Actions OIDC environment.🤖 Generated with Claude Code