Fix on branch stage-stream-artifact (207517f4): staging streams the artifact into a per-call temp file while hashing (stageArtifactFrom, OpenQualified). A regression test stages a 96 MB build and checks allocation stays under 8 MB. Astra round 1 found a P1 (a shared temp name under concurrent writers), fixed; round 2 accepted. Awaiting CI and a merge decision. Not released or rolled.
Staging a release reads the whole build into memory; a ~99 MB build kills the 256 MB stage pass closed
Claimed by claude~paul@keymail.dev 2 weeks ago
What happens
A box stages a new app release by reading the whole build into memory. stageFrom (internal/release/adopt.go) calls GetQualified, which returns the artifact as a []byte (S3Store.Get → io.ReadAll), hashes it with etagOf, and hands the bytes to StageArtifact → WriteFileAtomic.
Every root pass that stages runs under MemoryMax=256M (carlos-stage.service, carlos-adopt.service). io.ReadAll holds about twice the file while its buffer grows, and the Go runtime and AWS SDK sit on top. A ~99 MB build peaked at 256 MB and the pass was killed (seen on a deployment's staging box, 2026-10-09).
What it costs
- A console promote is noticed within seconds and relayed to
carlos-stage.service. That pass is killed, so the release waits for the next hourlycarlos-adopt.timerpass. - With the stock units, the hourly pass is capped at 256 MB too, so a large enough build never goes live at all.
- The deployment that found this survives only because someone raised
carlos-adopt.serviceto 512 MB by hand, and its CI promotes direct-bucket, so it never takes the stage path. Moving its CI to the console path (#229'sdeployrole) would make every deploy fail fast and then wait up to an hour.
Fix
Stream the artifact into the version store: open it with OpenObject, copy it into the temp file beside the destination while hashing, check the sha256, then chmod and rename. On a mismatch or a read error, remove the temp file and leave the destination untouched, as today. Keep the migration hint on a missing key (an OpenQualified beside GetQualified). Memory then stays flat whatever the build size, and 256 MB stays a sensible cap.
Out of scope here, worth their own look: edge.ensureExtracted (internal/edge/static.go) and vet also read whole artifacts with GetQualified.
Stopgap for deployments until the fix ships
# /etc/systemd/system/carlos-stage.service.d/memory.conf
[Service]
MemoryMax=512Mthen systemctl daemon-reload. Remove it (and any adopt-pass equivalent) once boxes run a release with the fix.
Merged to main as cd987f6 (squash of stage-stream-artifact at 3930f7fe, CI green after merging main). GitHub mirror fast-forwarded. Not yet released or rolled: deployments carrying a 512 MB MemoryMax drop-in on carlos-stage.service or carlos-adopt.service should remove it once their boxes run the first release containing cd987f6.