* ci: a test-only change under studio or player needs no captures
Tests are not behaviour a user sees, like the markdown the check already skips.
A bench-harness PR (#4953) was asked for Before and After screenshots.
* ci: pin a mixed source and test change, and say tests are not counted
* fix(ci): let markdown-only changes under studio and player skip the captures check
* docs: map the Studio edit path, the edit accuracy bench and test setup for agents
* docs: name the Studio captures rule and the local comment checks
* docs: point the Studio gate notes at the scripts that own the rules
* docs: correct the Studio save paths and add the producer stage map
* docs: name where producer sizes workers and which captures encode inline
* fix(scripts): retry a fresh GitHub attachment's transient 404 in the captures gate
Reproduced this week: the same attachment URL 404'd, then 200'd minutes
later with no edit to the PR in between, and whichever asset had been
attached most recently was always the one that failed. The gate already
retries 429/5xx with backoff, but treats every other status as final —
so a fresh upload lagging GitHub's own read-path failed the check on
attempt 1.
404 is now retryable, scoped to a GitHub attachment host (isAttachmentUrl)
so a real 404 elsewhere (a typo'd link, a deleted gist) still fails fast.
403 stays non-retryable everywhere, including on an attachment host — it
means something else (auth, a private-repo asset another workflow can't
see), not propagation lag.
Widened the retry window to match: 5 attempts, delays out to 60s, 3-minute
deadline (was 3 attempts / 45s) — the observed lag was up to ~3 minutes.
Tests: split the old combined 404-and-403 test; added the attachment-host
404 retry case and the non-attachment-host 404 still-final case; updated
the backoff test's attempt/delay assertions for the new constants. 73/73
green, oxlint/oxfmt clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(scripts): stop the captures-gate retry tests from sitting through the real backoff
Review on #4362: `runCli()`'s no-network preload stubs `fetch` but not time,
so the two CLI tests exercising a genuinely-undownloadable capture now sit
through the full 5-attempt/110s backoff per asset (two assets each) — 220s+
per test, pushing `bun run test:scripts` from 15s to 446s and `Test (support)`
(timeout-minutes: 10) to 10m0s with zero margin.
Preload now also collapses every `setTimeout` delay to the next tick, so the
retry logic itself still runs for real (same attempt count, same terminal
"could not download" outcome) — it just doesn't wait. 73/73 tests, 1.1s total.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(scripts): split isRetryableStatus to clear fallow's CRAP-score gate
CI's fallow audit flags `isRetryableStatus` at CRAP 30.0 (cyclomatic 5,
threshold 30.0) — pre-existing since the original commit, not something the
prior test-timing fix touched. Split into `isServerRetryable` (429/5xx) and
`isRetryableAttachment404` (404 scoped to the attachment host), each at
cyclomatic 2-3, composed by the still-exported `isRetryableStatus`. Same
retry decision, no behavior change. 73/73 tests, oxlint/oxfmt clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat(scripts): fail a PR whose After capture is a Before capture
The captures check now downloads each Before and After asset in the PR
body and fails when any After asset has the same content hash as a
Before asset, or when an asset cannot be downloaded. A Before clip
attached under After passed the old check because it only looked for
media, not for what the media was.
* refactor(scripts): return the hashed captures directly
* fix(scripts): the capture hash check only fetches GitHub attachments
The check fetched any URL a PR body named and followed redirects, so a
body could make the runner request an internal host. It now downloads
only github.com/user-attachments assets and follows redirects only to
GitHub and its signed storage. A 429, 5xx or network error is retried
with backoff, other statuses fail at once, and unreadable assets and
identical assets are reported under separate headings.
* fix(scripts): cap capture downloads, match GitHub's asset bucket, pass forks through
The redirect allowlist now names GitHub's asset bucket instead of all of amazonaws.com.
Each asset and the whole check are capped in bytes while streaming.
A fork PR whose capture cannot be downloaded is skipped with a named reason, never failed.
* fix(scripts): one download deadline, https only, same link caught before fetching
Reviewer findings on the capture hash check:
- each asset gets one deadline across its retries
- downloads run one at a time so a large asset cannot starve a sibling
- a link under both headings is reported without a download
- a read error is no longer reported as a download failure
- the CLI fork skip and exit code are tested through a preloaded fetch
* fix(scripts): a byte-cap refusal blocks even on a fork, retries refund their bytes
- our own cap tripping is reported apart from an unreadable download and always fails
- a failed attempt gives its bytes back to the shared budget
- trailing punctuation and query strings no longer change which asset a link names
- more than 12 capture links is refused, and the job timeout is raised to fit
* fix(scripts): strip markdown wrappers from capture links, count shared links once
The twelve-link limit is tested at its edge.
* refactor(scripts): split the capture download code into small functions
Fallow scored the download, redirect and reporting functions as untested because the scripts
suite runs under node --test. Each function is now small enough to pass without coverage.
Links the duplicate check cannot compare are now named: an http attachment link is refused,
other media links are listed.
* fix(scripts): compare links by identity, name the refusal, pin three behaviours
An explicit port or upper-case host no longer reads as an uncomparable link.