Three faults in gitlab/checkout and one in gitlab/read_file, all found by a QA agent that set up a Laravel application from an 885 MB project and spent about fifteen turns on the workshop rather than on the work (#1). Plus a settable browser viewport (#2).
The subtree landed inside itself. With ?path=stupla/public GitLab's archive carries the full repository path, and the extractor stripped only the shell directory (projname-ref-sha) before unpacking into the subdirectory — so the files ended up under stupla/public/stupla/public/. It was noticed because public/index.php was missing, after two fetches of 5,703 files each. Paths are now normalised to repository-relative whichever shape the server sends, and that is also what makes several partial checkouts grow into one tree, which was the intent all along. The old test did not catch it: its fixture archive was already trimmed — it tested the case the server does not send.
A partial checkout brought no build files. No artisan, no composer.json, no composer.lock — a PHP project cannot be built from that, and you find out at the first composer install. The agent's way out is the part nobody should have to write down: it called the full checkout knowing it would fail at the size limit, because the aborted fetch left the root files behind. The blobs in the directories above the subtree now come along, bounded per level and per file: build files, not a second way to pull a whole repository.
A tree in daily use looked like the oldest one. Eviction measures the mtime of the top directory, and a tree assembled from thirteen partial checkouts gets its files written inside it — the top never moves. It was as old as its first fetch while being the only one worked in, and five unrelated checkouts pushed it out. The root is touched on every checkout now, and what was evicted comes back as a field rather than as a sentence inside a long hint: a list does not get lost in a paragraph.
read_file can fetch the rest. The cut at 512 kB was reported properly ("truncated": true) and was still a trap: there was no way to get the remainder. composer.lock at 598,917 bytes arrived unusable, and whoever does not check the field builds with half a lock file — a state nobody can reproduce afterwards. read_file takes an offset now and says where the next piece starts, read through a Range header; a server that ignores Range answers 200 with the whole file, and the beginning is dropped rather than handed back twice.
The browser window size stops being a constant. Chrome started at a fixed 1440x900 and nothing changed it, so every page an agent ever saw was rendered at one width — responsive behaviour was not testable at all, and a screenshot filed against a ticket did not say what width it was taken at. There is a viewport action now, with named sizes (phone, tablet, desktop, wide) beside raw numbers, going through emulation rather than the window. screenshot takes the same parameters and puts the session back afterwards.
Requires SDK v0.4.0, which keeps every working copy used within the last half hour beyond COVEY_CHECKOUT_KEEP — that is what stops five checkouts passing through from removing the sixth.