netbirdio/netbird · warning

file already exists

Error message

file already exists

What it means

HTTP 409 returned when os.OpenFile(filePath, O_WRONLY|O_CREATE|O_EXCL, 0600) fails with EEXIST. The O_EXCL flag makes create-or-fail atomic, so a file already exists at that exact path; the handler deliberately answers Conflict instead of overwriting, unlike S3 PUT which would replace the object.

Source

Thrown at upload-server/server/local.go:138

	filePath := filepath.Clean(filepath.Join(dirPath, uploadFile))
	if !strings.HasPrefix(filePath, cleanBase) {
		http.Error(w, "invalid path", http.StatusBadRequest)
		log.Warnf("Path traversal attempt blocked (file): %s", filePath)
		return
	}

	if err = os.MkdirAll(dirPath, 0750); err != nil {
		http.Error(w, "failed to create upload dir", http.StatusInternalServerError)
		log.Errorf("Failed to create upload dir: %v", err)
		return
	}

	flags := os.O_WRONLY | os.O_CREATE | os.O_EXCL
	f, err := os.OpenFile(filePath, flags, 0600)
	if err != nil {
		if os.IsExist(err) {
			http.Error(w, "file already exists", http.StatusConflict)
			return
		}
		http.Error(w, "failed to create file", http.StatusInternalServerError)
		log.Errorf("Failed to create file %s: %v", filePath, err)
		return
	}
	defer func() { _ = f.Close() }()

	if _, err = f.Write(body); err != nil {
		http.Error(w, "failed to write file", http.StatusInternalServerError)
		log.Errorf("Failed to write file %s: %v", filePath, err)
		return
	}

	log.Infof("Uploaded file %s", filePath)
	w.WriteHeader(http.StatusOK)
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. On 409, request a fresh URL from GET /upload-url (it generates a new <id>/<uuid> key) and PUT again
  2. Make 'fetch a new URL' part of every retry step; never replay the same PUT URL
  3. If you need idempotent overwrite semantics, run the S3 backend (BUCKET + AWS_REGION set) instead of local storage

Example fix

// before: replaying the same URL; second attempt gets 409
for i := 0; i < retries; i++ { httpPut(url, body) }

// after: fresh URL per attempt
for i := 0; i < retries; i++ {
	url := getUploadURL(id) // GET /upload-url -> new <id>/<uuid> key
	if err := httpPut(url, body); err == nil { break }
}
Defensive patterns

Strategy: validation

Validate before calling

// always obtain a fresh key per upload attempt
url, err := getUploadURL(ctx, id) // GET /upload-url?id=<id>
if err != nil {
	return err
}
// PUT to url once; on any failure loop back to getUploadURL, never reuse url

Try / catch

If resp.StatusCode == 409 ('file already exists'), the key is burned: fetch a new URL from GET /upload-url and PUT there. Retrying the same URL always returns 409.

Prevention

When it happens

Trigger: Retrying a PUT with the same upload URL/key after a previous (even partial) upload created the file; re-using a previously fetched upload URL instead of requesting a new one; theoretically a UUID collision in the generated key.

Common situations: Client retry logic that replays the same PUT on timeout; two racing uploads consuming the same URL; upload frameworks that assume S3-style overwrite semantics against the local backend.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/d377db1056bc31de. Report an issue: GitHub.