Workhorse artifact metadata temp path falls back to os.TempDir() when using object storage direct upload, causing TMPDIR mismatch with Rails
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Summary
When object storage with direct upload is enabled and a user configures a custom TMPDIR via gitlab_rails['env'] (as documented in the Omnibus environment variables docs), CI artifact uploads fail with 400 Bad Request and Content-Type: text/plain.
The root cause is in workhorse/internal/upload/artifacts_uploader.go. When Workhorse generates artifact metadata (metadata.gz) from a ZIP artifact, it needs a local temp directory. If the Rails /authorize response provides a RemoteObject (object storage direct upload) rather than a TempPath, the tempDir field is empty and Workhorse falls back to os.TempDir():
func (a *artifactsUploadProcessor) generateMetadataFromZip(...) {
metaOpts := &destination.UploadOpts{
LocalTempPath: a.tempDir,
}
if metaOpts.LocalTempPath == "" {
metaOpts.LocalTempPath = os.TempDir() // Falls back to TMPDIR env var or /tmp
}
// ...
}Rails' multipart middleware (lib/gitlab/middleware/multipart.rb) then validates the metadata temp file path against allowed_paths, which includes Dir.tmpdir (Ruby's equivalent, also respecting TMPDIR). If the user set gitlab_rails['env'] = { 'TMPDIR' => '/var/opt/gitlab/tmp' } but did not set gitlab_workhorse['env'] to match, Dir.tmpdir returns /var/opt/gitlab/tmp while the metadata file is in /tmp. The path validation fails, raising InvalidPathError, which the middleware catches and returns as 400 Bad Request with Content-Type: text/plain:
rescue UploadedFile::InvalidPathError, ApolloUploadServer::GraphQLDataBuilder::OutOfBounds => e
[400, { 'Content-Type' => 'text/plain' }, [e.message]]
endThis is the same class of issue as #363701 (closed), which was caused by !87255 (merged) and reverted in !90083 (merged). The revert fixed the local storage case by restoring the use of a.tempDir from the Rails authorize response, but the os.TempDir() fallback for the object storage direct upload path was left in place.
Steps to reproduce
- Configure a GitLab Omnibus instance with object storage for artifacts (direct upload enabled).
- Set a custom TMPDIR for Rails only in
/etc/gitlab/gitlab.rb:gitlab_rails['env'] = { 'TMPDIR' => '/var/opt/gitlab/tmp' } - Create the directory with appropriate permissions:
sudo mkdir -p /var/opt/gitlab/tmp sudo chown git:git /var/opt/gitlab/tmp sudo chmod 700 /var/opt/gitlab/tmp - Run
sudo gitlab-ctl reconfigure. - Run a CI job that produces artifacts.
Example Project
N/A - reproducible on any project with CI artifact uploads when the above configuration is applied.
What is the current bug behavior?
Artifact uploads fail with 400 Bad Request. Workhorse logs show:
artifacts.zipuploaded successfully viaclient_mode: "s3_client_v2"(direct to object storage)metadata.gzwritten asclient_mode: "local_tempfile"withlocal_temp_path: "/tmp"- The finalize POST to Rails returns
status: 400withcontent_type: "text/plain"
What is the expected correct behavior?
Artifact uploads should succeed regardless of whether gitlab_workhorse['env'] has TMPDIR set, because Workhorse should use a temp path that Rails will accept. The metadata temp file should be written to a directory that Rails' multipart middleware considers allowed.
Relevant logs and/or screenshots
Workhorse logs for a failing upload (from a customer report, redacted):
{"client_mode":"s3_client_v2","correlation_id":"...","filename":"artifacts.zip","is_local":false,"is_remote":true,"msg":"Destination: Upload saved file","provider":"AWS"}
{"client_mode":"local_tempfile","correlation_id":"...","filename":"metadata.gz","is_local":true,"is_remote":false,"local_temp_path":"/tmp","msg":"Destination: Upload saved file"}
{"content_type":"text/plain","correlation_id":"...","status":400,"uri":"/api/v4/jobs/.../artifacts?artifact_format=zip&artifact_type=archive"}Output of checks
N/A - this is a code-level issue, not environment-specific.
Results of GitLab environment info
Reproduced on GitLab 18.8.5 (Omnibus) with object storage (AWS S3, use_iam_profile).
Results of GitLab application Check
N/A
Possible fixes
Several approaches could resolve this:
-
Have Rails include a metadata temp path in the
/authorizeresponse even when using direct upload. Currently, when direct upload is enabled, Rails returns aRemoteObjectbut noTempPath. Rails could include aTempPathspecifically for metadata generation, so Workhorse never needs to fall back toos.TempDir(). This follows the existing design pattern where Rails tells Workhorse where to write. -
Add
/tmpunconditionally toallowed_pathsin the multipart middleware. This would ensure the hardcoded fallback always works, but is less clean since it assumes/tmpis always a valid and accessible path. -
Have Omnibus ensure both components inherit the same
TMPDIRat the packaging/configuration level, rather than relying on users to configure them independently. This would be a configuration-level fix rather than a code fix.
Option 1 is likely the cleanest approach as it follows the existing architecture where Rails is the source of truth for upload paths.
Related issues and MRs
- #363701 (closed) - Original report of this class of issue (closed, fixed for local storage only)
- !87255 (merged) - "Use OS tempdir for artifact metadata" (introduced the
os.TempDir()fallback) - !90083 (merged) - Revert of the above (fixed local storage case, left object storage fallback)
- omnibus-gitlab!6152 (closed) - Previous docs MR attempt (closed without merging)
- omnibus-gitlab!9204 (merged) - Documentation workaround (in progress)