{"record":{"id":"882df215e83bc25c","repo":"hibiken/asynq","slug":"revoke-task","errorCode":null,"errorMessage":"revoke task","messagePattern":"revoke task","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"processor.go","lineNumber":333,"sourceCode":"\t\terrMsg := fmt.Sprintf(\"Could not remove task id=%s type=%q from %q err: %+v\", msg.ID, msg.Type, base.ActiveKey(msg.Queue), err)\n\t\tp.logger.Warnf(\"%s; Will retry syncing\", errMsg)\n\t\tp.syncRequestCh <- &syncRequest{\n\t\t\tfn: func() error {\n\t\t\t\treturn p.broker.Done(ctx, msg)\n\t\t\t},\n\t\t\terrMsg:   errMsg,\n\t\t\tdeadline: l.Deadline(),\n\t\t}\n\t}\n}\n\n// SkipRetry is used as a return value from Handler.ProcessTask to indicate that\n// the task should not be retried and should be archived instead.\nvar SkipRetry = errors.New(\"skip retry for the task\")\n\n// RevokeTask is used as a return value from Handler.ProcessTask to indicate that\n// the task should not be retried or archived.\nvar RevokeTask = errors.New(\"revoke task\")\n\nfunc (p *processor) handleFailedMessage(ctx context.Context, l *base.Lease, msg *base.TaskMessage, err error) {\n\tif p.errHandler != nil {\n\t\tp.errHandler.HandleError(ctx, NewTaskWithHeaders(msg.Type, msg.Payload, msg.Headers), err)\n\t}\n\tswitch {\n\tcase errors.Is(err, RevokeTask):\n\t\tp.logger.Warnf(\"revoke task id=%s\", msg.ID)\n\t\tp.markAsDone(l, msg)\n\tcase msg.Retried >= msg.Retry || errors.Is(err, SkipRetry):\n\t\tp.logger.Warnf(\"Retry exhausted for task id=%s\", msg.ID)\n\t\tp.archive(l, msg, err)\n\tdefault:\n\t\tp.retry(l, msg, err, p.isFailureFunc(err))\n\t}\n}\n\nfunc (p *processor) retry(l *base.Lease, msg *base.TaskMessage, e error, isFailure bool) {","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/processor.go#L315-L351","documentation":"RevokeTask is a sentinel returned from Handler.ProcessTask to indicate the task should be neither retried nor archived. In processor.handleFailedMessage, errors.Is(err, RevokeTask) causes the processor to simply mark the message as done (markAsDone), dropping it as if it had completed successfully.","triggerScenarios":"A Handler.ProcessTask returns RevokeTask (or an error wrapping it with %w); processor.go:340 matches it in the switch and marks the task done instead of retrying or archiving it.","commonSituations":"Situations where a task becomes moot while processing — the resource it acted on was deleted, a superseding task already handled the work, or a shutdown/cancellation makes the work pointless — and the developer wants silent disposal rather than polluting the archive.","solutions":["If the task should be re-attempted later, return a regular error (not RevokeTask) so retry policy kicks in.","If the task should be kept for inspection, return SkipRetry instead of RevokeTask so it's archived rather than discarded.","Make sure RevokeTask is wrapped with %w (or returned directly); wrapping with %v breaks errors.Is and the task will be retried/archived instead of revoked.","Log the revoke decision inside the handler, since the processor only logs 'revoke task id=...' and no error handler distinction beyond that."],"exampleFix":"// before\nif jobCancelled {\n    return fmt.Errorf(\"cancelled: %v\", asynq.RevokeTask) // %v breaks detection; task gets retried\n}\n// after\nif jobCancelled {\n    return fmt.Errorf(\"cancelled: %w\", asynq.RevokeTask) // task dropped without retry/archive\n}","handlingStrategy":"try-catch","validationCode":"// check preconditions inside the handler before deciding to revoke\nif resourceDeleted(task.Payload()) { return fmt.Errorf(\"resource gone: %w\", asynq.RevokeTask) }","typeGuard":"func isRevokeTask(err error) bool { return errors.Is(err, asynq.RevokeTask) }","tryCatchPattern":"if err := handler.ProcessTask(ctx, task); err != nil {\n    if errors.Is(err, asynq.RevokeTask) {\n        log.Printf(\"task %s revoked (dropped, no retry/archive)\", task.ID())\n    }\n    return err\n}","preventionTips":["Use RevokeTask only when the task is truly moot; prefer SkipRetry when you want an audit trail in the archive.","Always wrap with %w so the processor's errors.Is switch matches.","Log inside the handler when revoking, since revoked tasks leave no trace otherwise.","Ensure upstream producers don't enqueue tasks for resources that can disappear mid-flight, or handle it via revoke."],"tags":["go","task-handler","sentinel-error","revoke","discard-task"],"backgroundTag":"invalid-state-transition","analyzedSha":"d135f1439bee74e989b7f9b41ecd542cc87f024a","analyzedAt":"2026-09-07T19:02:34.660Z","contentChangedAt":"2026-09-07T19:02:34.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}