Return the owning Tempfile from Download.open_each - #73
Merged
Merged
Conversation
A remote or IO-backed source is held in a Tempfile, which unlinks its path once it is garbage collected. open_each returned a fresh File opened on that path and dropped the Tempfile, so a caller that kept the result and read its path later (a cache key, EXIF parsing, an image handler) could find it gone with ENOENT. Return the Tempfile itself for the non-archive case, so the path stays valid for as long as the caller holds the result. Archive entries are ordinary files on disk and are still opened by path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd
json 3.0.0 (released 2026-09-07) removed the positional options argument to JSON.parse. ActiveSupport::JSON.decode in every released Rails still passes one, so reading any JSON column raises ArgumentError and the feature import and queued processing specs fail on the Ruby 4.0 job. Rails has the fix on main (rails/rails#58601) but no release yet. Pin json to 2.x in both CI gemfiles until a fixed Rails ships. This is a test-matrix constraint only; the gemspec is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd
rywall
added a commit
that referenced
this pull request
Sep 8, 2026
Releases the two commits merged since 3.12.0: - 66d4859 Add EXIF photo importer (#72) - d366ac3 Return the owning Tempfile from Download.open_each (#73) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwZduP5HbaZc6i2hoJwFJ5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Download.open_eachopens a remote or IO-backed source into aTempfile, then returns a freshFileon that path and drops theTempfile. Nothing references it after that, so the next garbage collection runs its finalizer and unlinks the path. A caller that keeps the returned file and reads its path later getsErrno::ENOENT.Today this is latent:
KMLFilereads the bytes in the same expression asopen_each, and the realistic remote inputs forShapefileandFileare archives whose extracted entries are ordinary files. The EXIF photo importer in #72 is the first caller whose main input is a single remote file and which reads the path lazily, acrosscache_key, EXIF parsing, and the app's image handler. It currently works around this by copying each photo into a directory it owns.Change
open_eachreturns theTempfileitself for the non-archive case, so the path stays valid for as long as the caller holds the result. Archive entries are still opened by path. Local paths are unchanged.Verification
StringIOfor small bodies, aTempfilefor large ones) and check the path is still readable afterGC.start. They fail on master and pass here. A 20-run check showed the old code lost the path in every run and the new code kept it in every run.🤖 Generated with Claude Code
https://claude.ai/code/session_01QME46q3GqY6JQZFwZdnbcd