Skip to content

fix: pin archive DerivedData to the worktree (stop the ~2 GB/ship leak) - #3

Merged
tshiv merged 1 commit into
mainfrom
claude/pushback-deriveddata-leak-6ad357
Aug 3, 2026
Merged

fix: pin archive DerivedData to the worktree (stop the ~2 GB/ship leak)#3
tshiv merged 1 commit into
mainfrom
claude/pushback-deriveddata-leak-6ad357

Conversation

@tshiv

@tshiv tshiv commented Aug 2, 2026

Copy link
Copy Markdown
Owner

What

xcodebuild archive was the only one of pushback's four xcodebuild invocations without a -derivedDataPath. It fell back to Xcode's default location, ~/Library/Developer/Xcode/DerivedData/<Project>-<hash>, where the hash is derived from the .xcodeproj absolute path.

Shipping from git worktrees therefore minted a separate 2 to 3 GB cache for every worktree path, and Xcode never reclaims those when the worktree is deleted. Found in the wild at 15 orphaned directories totaling ~30 GB against 7 live worktrees, growing by one cache per ship from a fresh worktree.

The archive now builds into $APP_DIR/build/archive, which is worktree-local by construction ($APP_DIR is $REPO_ROOT/$PUSHBACK_APP_DIR), so the cache dies with the checkout.

Changes

  • pushback — new ARCHIVE_BUILD_DIR="$APP_DIR/build/archive" alongside the other path vars; -derivedDataPath "$ARCHIVE_BUILD_DIR" on the archive invocation; pre-clean extended to cover it; post-use rm -rf right after the upload.
  • CHANGELOG.md — entry under [Unreleased] → Fixed, including the build-time tradeoff.
  • README.md — new ### Build artifacts subsection under Usage (the README previously documented neither artifacts nor cleanup).

Audit of the other call sites

Call -derivedDataPath? Action
build (verify) yes, $VERIFY_BUILD_DIR none
test (QA) yes, $VERIFY_BUILD_DIR none
archive no fixed
-exportArchive n/a none, works off -archivePath and never touches DerivedData

Only the archive needed the flag.

Reviewer notes

Cleanup follows the existing convention, not a new one. Clean before use (extending the rm at the top of the archive step, mirroring the rm -rf "$VERIFY_BUILD_DIR" before the verify build) and clean after use (before step_done, same as the verify and QA steps). The cleanup_on_failure trap is deliberately untouched: it handles only version-bump/push recovery and stash restore today, and VERIFY_BUILD_DIR is likewise left behind on a failed verify. The pre-clean wipes any leftover on the next run, and the directory is worktree-local either way.

Dry-run. The new rm -rf is not gated on DRY_RUN, matching all five existing rm -rf calls in the script. run_xcodebuild short-circuits before invoking xcodebuild in dry-run, so no DerivedData is ever produced and the rm -rf is a no-op on a nonexistent path.

Tradeoff (documented in the CHANGELOG so it isn't misfiled as a regression later). The existing rm -rf "$APP_DIR/build" at the end of a successful ship means the release archive is now always a cold build; previously the default-location cache persisted and a second ship from the same path reused it. This is a deliberate trade: under a worktree-per-feature workflow most ships start from a fresh worktree where there was no cache to reuse anyway, and a hermetic release archive is worth having on its own merits. If the wall-clock cost ever bites, the escape hatch is to stop deleting build/archive after the upload and exclude it from the final sweep, giving one warm cache per live worktree, bounded by worktree count instead of unbounded.

Verification

  • bash -n and shellcheck clean.
  • Dry run from a throwaway flip-calculator worktree (the condition that reproduces the bug, since the failure mode is distinct absolute paths). Completed with exit 0; bash -x trace shows the archive carrying -derivedDataPath .../worktrees/pb-ddleak-verify/ios/build/archive.
  • Real signing-free simulator build from that worktree with the flag. Dry-run alone can't prove the placement, since it stubs xcodebuild and creates no DerivedData either way. The real build put 3.2 GB entirely inside build/archive (Build/Products/Debug-iphonesimulator/FlipOrPass.app present) while ~/Library/Developer/Xcode/DerivedData stayed at 0 B.
  • Throwaway worktree and branch removed afterward.

🤖 Generated with Claude Code


Open in Devin Review

`xcodebuild archive` was the only one of pushback's four xcodebuild
invocations without a `-derivedDataPath`. It fell back to Xcode's default
`~/Library/Developer/Xcode/DerivedData/<Project>-<hash>`, where the hash is
derived from the .xcodeproj ABSOLUTE path.

Shipping from git worktrees therefore minted a separate 2-3 GB cache per
worktree path, and Xcode never reclaims those when the worktree is deleted.
Found in the wild at 15 orphaned directories, ~30 GB, against 7 live worktrees,
growing by one cache per ship from a fresh worktree.

The archive now builds into `$APP_DIR/build/archive`, which is worktree-local
by construction ($APP_DIR is $REPO_ROOT/$PUSHBACK_APP_DIR), so the cache dies
with the checkout. Cleanup follows the existing VERIFY_BUILD_DIR convention:
clean before use (extending the rm at the top of the archive step) and clean
after use (right after the upload, before step_done). The trap is deliberately
untouched, matching how the script already treats build artifacts on failure.

Audited the other three call sites: verify build and test already pin to
$VERIFY_BUILD_DIR; -exportArchive works off -archivePath and never touches
DerivedData. No other invocation needs the flag.

Tradeoff, documented in the CHANGELOG so it isn't misfiled as a regression:
the final `rm -rf "$APP_DIR/build"` means the release archive is now always a
cold build. Under a worktree-per-feature workflow most ships start from a fresh
worktree where no cache existed to reuse, and a hermetic release archive is
worth having regardless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pushback Ready Ready Preview Aug 2, 2026 11:26pm

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@tshiv
tshiv merged commit 26e3fef into main Aug 3, 2026
4 checks passed
@tshiv
tshiv deleted the claude/pushback-deriveddata-leak-6ad357 branch August 3, 2026 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant