build infrastructure · SEV-3
build agents die mid-job with lost-contact and process-spawn errors that name the wrong component entirely
How to confirm it
Separate eviction from cancellation first
kubectl -n NS get events --sort-by=.lastTimestamp | grep -iE 'evict|oom'Spawn failures and lost-agent messages are tear-down artifacts of a pod dying mid-job, not causes. The event tells you whether it was evicted or simply cancelled by a rapid re-push.
Check the limit, not the disk
kubectl -n NS exec POD -- df -h /tmp/workPlenty of free space on the node alongside an eviction message means it was limit enforcement, not physical exhaustion. That reframes the whole investigation.
Catch the volume-type drift
kubectl -n NS get scaledjob NAME -o jsonpath='{.spec.jobTargetRef.template.spec.volumes}' | jqCompare against committed source. An emptyDir counts toward the ephemeral limit; a host path does not. A sibling pool on host path being unaffected is how the drift was localised.
Spot a poisoned partial cache in the log
grep -E 'cache hit|Downloading' build.log | head -30A reported cache hit followed by downloads of everything means the archive is a sliver. Changing one line in the requirements file changes the key — and if that first build dies, the partial is what every later build inherits.
Patch surgically on a drifted resource
kubectl -n NS patch scaledjob NAME --type=json -p '[{"op":"replace","path":"/spec/.../ephemeral-storage","value":"50Gi"}]'A full apply of committed source would wipe the teammate hand-edits that are keeping the pool working. Single-field patches, and confirm with the owner.
Read the source
Cause
Two compounding faults. The workspace volume was an in-memory-class ephemeral volume, which counts against the container's ephemeral-storage limit, and the committed manifest said otherwise — that was live drift from a hand edit. Separately the dependency cache was keyed on a content hash, and the first build on a new key had been killed mid-download, so it saved a partial archive. The cache layer only writes on a miss and never overwrites a hit, so every later build hit that partial cache, re-downloaded the rest, bloated the workspace past the limit, got evicted before finishing, and never saved a good cache. A closed loop.
Fix
Raise the ephemeral limit enough for a full cold download to complete — one green run then saves a complete cache and the loop self-heals. Size it from the node's actual disk, not optimism: a limit larger than the disk guarantees node-level pressure that evicts every pod on the node, which is strictly worse than a per-pod limit.