AlistGo/alist · error

obj already exists

Error message

obj already exists

What it means

PutURL checks that the final object (dstDirPath/dstName) does not already exist before fetching by URL; if a GetUnwrap on that path succeeds, it returns 'obj already exists' rather than overwriting. This is an overwrite-protection guard, mirroring Put semantics for URL-based transfers.

Source

Thrown at internal/op/fs.go:613

			if err != nil {
				return err
			} else {
				key := Key(storage, stdpath.Join(dstDirPath, file.GetName()))
				linkCache.Del(key)
			}
		}
	}
	return errors.WithStack(err)
}

func PutURL(ctx context.Context, storage driver.Driver, dstDirPath, dstName, url string, lazyCache ...bool) error {
	if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
		return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
	}
	dstDirPath = utils.FixAndCleanPath(dstDirPath)
	_, err := GetUnwrap(ctx, storage, stdpath.Join(dstDirPath, dstName))
	if err == nil {
		return errors.New("obj already exists")
	}
	err = MakeDir(ctx, storage, dstDirPath)
	if err != nil {
		return errors.WithMessagef(err, "failed to put url")
	}
	dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
	if err != nil {
		return errors.WithMessagef(err, "failed to put url")
	}
	switch s := storage.(type) {
	case driver.PutURLResult:
		var newObj model.Obj
		newObj, err = s.PutURL(ctx, dstDir, dstName, url)
		if err == nil {
			if newObj != nil {
				addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
			} else if !utils.IsBool(lazyCache...) {
				ClearCache(storage, dstDirPath)

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Delete or rename the existing object, then retry the PutURL
  2. Generate a unique destination name (suffix with timestamp or task id) for retries
  3. Pre-check existence in your job logic and skip if the object is already the desired result

Example fix

// before
err := op.PutURL(ctx, storage, dir, "movie.mp4", url)
// after: skip when the target already exists
if _, err := op.GetUnwrap(ctx, storage, stdpath.Join(dir, "movie.mp4")); err == nil {
    return nil // already transferred
}
err := op.PutURL(ctx, storage, dir, "movie.mp4", url)
Defensive patterns

Strategy: validation

Validate before calling

// Before PutURL
if _, err := op.GetUnwrap(ctx, storage, stdpath.Join(dstDirPath, dstName)); err == nil {
    return nil // already exists — skip or rename
}

Try / catch

if err := op.PutURL(ctx, storage, dir, name, u); err != nil {
    if strings.Contains(err.Error(), "obj already exists") {
        return nil // treat retry as success if idempotency is desired
    }
    return err
}

Prevention

When it happens

Trigger: Calling op.PutURL with a dstName that already exists in dstDirPath — e.g. re-running an offline transfer or URL upload to the same target without deleting the previous output.

Common situations: Retried offline-download/transfer jobs that do not generate unique names; duplicate submissions of the same link; idempotent-retry logic that expects overwrite but gets refusal.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/b09c61366540af3f. Report an issue: GitHub.