cloudreve/cloudreve · error

failed to unlink entity: %v

Error message

failed to unlink entity: %v

What it means

UnlinkEntity removes the file from the entity's m2m file edge and decrements reference count in one Entity.UpdateOne(...).Exec. Failure means the database rejected that update: the entity row no longer exists, a join-table problem, or connectivity. The storage diff is only returned on success.

Source

Thrown at inventory/file.go:887

		versionCount++
		if versionCount > max {
			// By default, eager-loaded entity is sorted by ID in descending order.
			// So we can just unlink the entity and it will be the older version.
			newDiff, err := f.UnlinkEntity(ctx, e, file, owner)
			if err != nil {
				return diff, fmt.Errorf("failed to cap file entities: %v", err)
			}

			diff.Merge(newDiff)
		}
	}

	return diff, nil
}

func (f *fileClient) UnlinkEntity(ctx context.Context, entity *ent.Entity, file *ent.File, owner *ent.User) (StorageDiff, error) {
	if err := f.client.Entity.UpdateOne(entity).RemoveFile(file).AddReferenceCount(-1).Exec(ctx); err != nil {
		return nil, fmt.Errorf("failed to unlink entity: %v", err)
	}

	return map[int]int64{owner.ID: entity.Size * int64(-1)}, nil
}

func (f *fileClient) IsStoragePolicyUsedByEntities(ctx context.Context, policyID int) (bool, error) {
	res, err := f.client.Entity.Query().Where(entity.StoragePolicyEntities(policyID)).Limit(1).All(ctx)
	if err != nil {
		return false, fmt.Errorf("failed to check if storage policy is used by entities: %v", err)
	}

	if len(res) > 0 {
		return true, nil
	}

	return false, nil
}

View on GitHub (pinned to 20c95ad73f)

Solutions

  1. Treat ent not-found as already-unlinked (benign) and return an empty diff instead of failing
  2. Make the unlink idempotent and safe to retry
  3. Check database connectivity and inspect the wrapped %v for the driver-specific cause

Example fix

// before
if err := f.client.Entity.UpdateOne(entity).RemoveFile(file).AddReferenceCount(-1).Exec(ctx); err != nil {
	return nil, fmt.Errorf("failed to unlink entity: %v", err)
}
// after
if err := f.client.Entity.UpdateOne(entity).RemoveFile(file).AddReferenceCount(-1).Exec(ctx); err != nil {
	if ent.IsNotFound(err) {
		return map[int]int64{}, nil
	}
	return nil, fmt.Errorf("failed to unlink entity: %v", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := f.client.Entity.Get(ctx, entity.ID); err != nil {
	if ent.IsNotFound(err) {
		return map[int]int64{}, nil // already gone; nothing to unlink
	}
	return nil, err
}

Try / catch

diff, err := f.UnlinkEntity(ctx, entity, file, owner)
if err != nil {
	if ent.IsNotFound(err) {
		diff = map[int]int64{} // benign: entity already removed
	} else {
		return diff, err
	}
}

Prevention

When it happens

Trigger: Two workers unlinking the same entity concurrently; the entity deleted before the unlink executes; reference_count constrained below the decremented value (if a check constraint exists); database unavailable.

Common situations: Retention/cleanup jobs racing with upload completion or failure handlers; multi-worker deployments processing the same files.

Related errors


AI-assisted analysis of cloudreve/cloudreve@20c95ad73f (2026-08-16). Data as JSON: /api/errors/4b38d4ca7e03a669. Report an issue: GitHub.