vxcontrol/pentagi · error

failed to create resource directory %q: %w

Error message

failed to create resource directory %q: %w

What it means

After ensuring each path segment exists, ensureResourceDirs creates a directory row (IsDir=true). If tx.Create fails — and even after a unique-violation refetch the row is still missing or still a file — the create error is wrapped with this message. Typical causes are unique violations against a non-dir row (concurrent write), or generic DB failures.

Source

Thrown at backend/pkg/server/services/resources.go:2131

		rec := models.UserResource{
			UserID: uid,
			Hash:   "",
			Name:   path.Base(current),
			Path:   current,
			Size:   0,
			IsDir:  true,
		}
		if err := tx.Create(&rec).Error; err != nil {
			if isUniqueViolation(err) {
				refetched, ok, refetchErr := findResourceByPath(tx, uid, current)
				if refetchErr != nil {
					return nil, nil, nil, refetchErr
				}
				if ok && refetched.IsDir {
					continue
				}
			}
			return nil, nil, nil, fmt.Errorf("failed to create resource directory %q: %w", current, err)
		}
		created = append(created, rec)
	}

	return created, deleted, orphanHashes, nil
}

// deleteOrphanBlob removes the .blob for hash if no DB row references it.
func (s *ResourceService) deleteOrphanBlob(_ context.Context, hash string) {
	if hash == "" {
		return
	}
	var count int64
	if err := s.db.Model(&models.UserResource{}).
		Where("hash = ?", hash).
		Count(&count).Error; err != nil || count > 0 {
		return
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check the wrapped cause: if unique violation, re-list resources — the dir (or a conflicting file) now exists; retry the call.
  2. Serialize directory creation per user (lock/mutex or SELECT ... FOR UPDATE) if concurrent uploads of the same path are common.
  3. Verify DB is writable (disk space, read-only replica) if errors persist.
  4. If a file blocks the path, the segment loop already skipped it only when force=false errors out earlier; use force=true to remove the blocking file first.

Example fix

// before
_, err := svc.UploadResources(ctx, uid, "a/b/c", files, false) // concurrent upload raced on 'a/b'
// after
err := retry.OnError(3, isUniqueViolation, func() error {
    _, err := svc.UploadResources(ctx, uid, "a/b/c", files, false)
    return err
})
Defensive patterns

Strategy: retry

Validate before calling

for _, seg := range strings.Split(dirPath, "/") {
    if existing, ok, _ := svc.FindResourceByPath(uid, segPath); ok && !existing.IsDir {
        return fmt.Errorf("segment %q is a file", seg)
    }
}

Type guard

func isUniqueViolation(err error) bool {
    var pgErr *pgconn.PgError
    return errors.As(err, &pgErr) && pgErr.Code == "23505"
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to create resource directory") {
    if isUniqueViolation(errors.Unwrap(err)) {
        // re-list resources and retry once; the concurrent writer likely finished
    }
}

Prevention

When it happens

Trigger: Any of promoteToResources/UploadResources/move*/copyMultipleSources calls with a dirPath segment that (a) does not exist and the INSERT fails (unique violation from a concurrent request creating the same path as a file, connection drop), or (b) hits a unique violation but the refetched row is not a directory.

Common situations: Two concurrent uploads creating the same directory tree race with each other; a file with the same path was created between the findResourceByPath check and the Create; database is read-only or out of disk.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/44939d13fc50084f. Report an issue: GitHub.