Skip to content

Conversation

@bearcage-dayjob
Copy link

This commit adds configurable support for the no_tmp_dir URL parameter in gocloud.dev/blob/fileblob to address cross-filesystem rename issues.

The blob storage backend uses atomic rename operations (create temp file in os.TempDir(), then rename to final destination). This fails with "invalid cross-device link" errors when /tmp is on a different filesystem than the storage directory, which happens to be the case for my workspace VM.

This diff adds a BlobStorageOptions struct with a NoTempDir field that, when set to true, adds the ?no_tmp_dir=true parameter to file:// URLs, which tells fileblob to create temporary files in the same directory as the final destination. This can result in some untidiness in .tmp in the cache directory, but there's not really a universal solution to that. We could probably add some sort of "cleanup old ctime records on fetch" task to tidy up the user's log cache in the future, if this becomes a problem.

@wolfeidau
Copy link
Member

@bearcage-dayjob thanks for raising this, I was able to reproduce it.

I am working on a refactored version of this change based on what you have provided.

@bearcage-dayjob
Copy link
Author

Awesome, thanks for taking a look!

Alex Berghage and others added 2 commits November 10, 2025 13:43
This commit adds configurable support for the no_tmp_dir URL parameter
in gocloud.dev/blob/fileblob to address cross-filesystem rename issues.

Background:
The blob storage backend uses atomic rename operations (create temp file
in os.TempDir(), then rename to final destination). This fails with
"invalid cross-device link" errors when the temp directory (typically /tmp)
is on a different filesystem than the storage directory. This commonly
occurs in containerized environments where /tmp is on an overlay filesystem
and the cache directory is on a mounted volume.

Solution:
Added a BlobStorageOptions struct with a NoTempDir field that, when set to
true, adds the ?no_tmp_dir=true parameter to file:// URLs. This tells
fileblob to create temporary files in the same directory as the final
destination, avoiding cross-filesystem renames.

Changes:
- Added BlobStorageOptions struct with NoTempDir bool field
- Updated NewBlobStorage() to accept optional *BlobStorageOptions parameter
- Modified GetDefaultStorageURL() to accept noTempDir bool and append URL parameter
- Added helper functions for URL parameter manipulation
- Updated all existing callers to pass nil for backward compatibility
- Added comprehensive tests for new functionality
- All existing tests pass, confirming backward compatibility

Usage:
// Default behavior (backward compatible)
storage, err := buildkitelogs.NewBlobStorage(ctx, storageURL, nil)

// Enable no_tmp_dir to avoid cross-filesystem issues
opts := &buildkitelogs.BlobStorageOptions{NoTempDir: true}
storage, err := buildkitelogs.NewBlobStorage(ctx, storageURL, opts)

Trade-offs:
When NoTempDir=true, temporary files are created in the storage directory
instead of /tmp. This may result in stranded .tmp files if the process
crashes before cleanup runs, but prevents cross-filesystem rename failures.

Downstream consumers (like the MCP server or CLI tools) can now opt into
using no_tmp_dir by passing BlobStorageOptions when creating blob storage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@wolfeidau wolfeidau force-pushed the fix-cross-filesystem-renames branch from 575a6cf to 6ddfb04 Compare November 10, 2025 02:44
Copy link
Member

@wolfeidau wolfeidau left a comment

Choose a reason for hiding this comment

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

👍🏻 looks good with some ammendments

storageURL, err := GetDefaultStorageURL(storageURL)
//
// The opts parameter allows configuring blob storage behavior. Pass nil to use default options.
func NewBlobStorage(ctx context.Context, storageURL string, opts *BlobStorageOptions) (*BlobStorage, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

i've got a mild preference for using functional options rather than nil-able options struct, but its very mild

@wolfeidau wolfeidau merged commit cfa35d4 into buildkite:main Nov 10, 2025
1 check passed
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.

3 participants