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
- Delete or rename the existing object, then retry the PutURL
- Generate a unique destination name (suffix with timestamp or task id) for retries
- 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
- Make transfer jobs idempotent: pre-check target existence
- Use unique names (timestamp/task-id suffix) for retried submissions
- Decide overwrite policy up front and encode it in job configuration
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
- 123 offline download cannot target the root directory, pick
- file_id is required
- same-name dirs cannot be Put
- invalid hash
- empty files are not allowed by baidu netdisk
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/b09c61366540af3f.
Report an issue: GitHub.