Skip to content

fix(xcresult): read a copy of the bundle instead of migrating the caller's - #1184

Open
dfrankland wants to merge 1 commit into
mainfrom
dylan/xcresult-read-a-copy
Open

fix(xcresult): read a copy of the bundle instead of migrating the caller's#1184
dfrankland wants to merge 1 commit into
mainfrom
dylan/xcresult-read-a-copy

Conversation

@dfrankland

Copy link
Copy Markdown
Member

Independent bug fix, no stack. Found while working on #1178 but unrelated to it — this is on the shared read path, so it fixes the default path too.

The bug

xcresulttool migrates a bundle that predates database.sqlite3 in place the first time it is read:

bundle format writable read-only
older (Data + Info.plist) mutateddatabase.sqlite3 written into it exit 64, no JUnit
already migrated untouched fine
Error: "database.sqlite3" couldn't be moved because you don't have
permission to access "test4.xcresult".

So today an upload either silently writes into a build artifact it was only asked to read, or fails outright when it cannot — and read-only artifact mounts are ordinary in CI. It is also why two concurrent readers of one bundle race to create the same file.

The fix

XCResult::new copies the bundle into a TempDir and reads that. The caller's directory is never written to and never needs to be writable.

The copy is unconditional rather than keyed on whether a migration would happen — sniffing the format to save a copy trades a correctness guarantee for work that takes well under a second on a 64 MB bundle.

The temp dir sits behind an Arc because XCResult is Clone and TempDir is not: every clone shares the copy, and it is removed when the last one drops.

Testing

test_reading_a_bundle_neither_writes_to_it_nor_needs_it_writable makes an un-migrated bundle read-only, reads it, restores permissions, and asserts both that the read succeeded and that the directory is byte-for-byte unchanged. Dropping the copy fails it with a read-only bundle must still be readable.

Full suite: 37 unit, 22 integration.

🤖 Generated with Claude Code

…ler's

`xcresulttool` migrates a bundle that predates `database.sqlite3` in place the
first time it is read. Two things follow, neither of them ours to do:

- an upload writes into a build artifact it was only asked to read, and
- the read fails outright when that directory is not writable:

      Error: "database.sqlite3" couldn't be moved because you don't have
      permission to access "test4.xcresult".

  which is `exit 64` and no JUnit at all, on the read-only artifact mounts CI
  systems hand out.

It is also why two concurrent readers of one bundle race to create the same file.

`XCResult::new` now copies the bundle into a `TempDir` and reads that, so the
caller's directory is never written to and never needs to be writable. The copy
is unconditional rather than keyed on whether a migration would happen: sniffing
the format to save a copy trades a correctness guarantee for work we already do
in well under a second on a 64 MB bundle.

The temp directory is held behind an `Arc` because `XCResult` is `Clone` and
`TempDir` is not — every clone shares the copy, and it is removed when the last
one drops.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Aug 31, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant