{"record":{"id":"126827bf14cb1b00","repo":"AlistGo/alist","slug":"task-errstr","errorCode":null,"errorMessage":"task.Errstr","messagePattern":"task\\.Errstr","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/yunpan360/util.go","lineNumber":831,"sourceCode":"\t\t}\n\t}\n\n\t// Keep prior behavior when the async task is still pending after the probe window.\n\treturn nil\n}\n\nfunc checkCookieAsyncTask(task CookieAsyncTask, toleratedErrnos map[int]struct{}) (bool, error) {\n\tif task.Status != 10 {\n\t\treturn false, nil\n\t}\n\tif task.Errno == 0 {\n\t\treturn true, nil\n\t}\n\tif _, ok := toleratedErrnos[task.Errno]; ok {\n\t\treturn true, nil\n\t}\n\tif strings.TrimSpace(task.Errstr) != \"\" {\n\t\treturn true, errors.New(task.Errstr)\n\t}\n\tif strings.TrimSpace(task.Action) != \"\" {\n\t\treturn true, fmt.Errorf(\"yunpan async task %s failed: errno=%d\", task.Action, task.Errno)\n\t}\n\treturn true, fmt.Errorf(\"yunpan async task failed: errno=%d\", task.Errno)\n}\n\nfunc (d *Yunpan360) openMove(ctx context.Context, srcName, dstPath string) error {\n\tsignParams := map[string]string{\n\t\t\"src_name\": srcName,\n\t\t\"new_name\": dstPath,\n\t}\n\treturn d.openPOST(ctx, \"File.move\", signParams, nil, signParams, nil, true)\n}\n\nfunc (d *Yunpan360) openDelete(ctx context.Context, targetPath string) error {\n\treturn d.openPOST(ctx, \"File.delete\", nil, nil, map[string]string{\n\t\t\"fname\": targetPath,","sourceCodeStart":813,"sourceCodeEnd":849,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/yunpan360/util.go#L813-L849","documentation":"checkCookieAsyncTask polls a 360 async operation (move/recycle return a task id). When the task reaches terminal status 10 with a non-zero Errno that is not tolerated, and the task carries an Errstr, that server message becomes this error (drivers/yunpan360/util.go:831). It means the async operation ultimately failed server-side.","triggerScenarios":"waitCookieAsyncTask polling a /file/move or /file/recycle task whose remote execution failed — e.g. source vanished mid-task, target folder deleted concurrently, quota exceeded, or session revoked while the task ran. Any cookie-mode move/recycle with resp.Data.IsAsync == true can end here.","commonSituations":"Large folder moves that race with user deletions in the 360 web UI; tasks abandoned across a cookie refresh; partial-failure errors from 360's backend after retries.","solutions":["Re-list both source and destination to confirm current state, then retry the operation if still needed","Read Errstr/errno — 'file not exists' means re-sync listings; session errors mean refresh the cookie","For recurring races, serialize move/delete operations so concurrent UI actions don't invalidate tasks","Retry once after refreshing the cookie if the errno indicates session expiry"],"exampleFix":"// before: treat any async error as fatal\nif err := d.waitCookieAsyncTask(ctx, resp.Data.TaskID); err != nil { return err }\n\n// after: tolerate benign 'already moved' errnos\nif err := d.waitCookieAsyncTask(ctx, resp.Data.TaskID, toleratedErrnos{errnoAlreadyDone: {}}); err != nil { return err }","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isAsyncTaskErr(err error) bool { return err != nil && (strings.Contains(err.Error(), \"async task\")) }","tryCatchPattern":"err := d.waitCookieAsyncTask(ctx, resp.Data.TaskID)\nif err != nil && isTransientAsyncErr(err) {\n    time.Sleep(backoff)\n    err = d.waitCookieAsyncTask(ctx, resp.Data.TaskID)\n}","preventionTips":["Pass toleratedErrnos for known-benign codes (e.g. 'already moved')","Refresh listings after failed async tasks — partial state is common","Avoid concurrent user mutations while long moves are in flight"],"tags":["yunpan360","async","polling","cookie","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}