{"record":{"id":"4ea4373a83abebc4","repo":"jstedfast/MailKit","slug":"the-donetoken-must-be-cancellable","errorCode":null,"errorMessage":"The doneToken must be cancellable.","messagePattern":"The doneToken must be cancellable\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"MailKit/Net/Imap/ImapClient.cs","lineNumber":1915,"sourceCode":"\t\t/// <exception cref=\"ImapCommandException\">\n\t\t/// The server replied to the NOOP command with a NO or BAD response.\n\t\t/// </exception>\n\t\t/// <exception cref=\"ImapProtocolException\">\n\t\t/// The server responded with an unexpected token.\n\t\t/// </exception>\n\t\tpublic override void NoOp (CancellationToken cancellationToken = default)\n\t\t{\n\t\t\tvar ic = QueueNoOpCommand (cancellationToken);\n\n\t\t\tengine.Run (ic);\n\n\t\t\tProcessNoOpResponse (ic);\n\t\t}\n\n\t\tvoid CheckCanIdle (CancellationToken doneToken)\n\t\t{\n\t\t\tif (!doneToken.CanBeCanceled)\n\t\t\t\tthrow new ArgumentException (\"The doneToken must be cancellable.\", nameof (doneToken));\n\n\t\t\tCheckDisposed ();\n\t\t\tCheckConnected ();\n\t\t\tCheckAuthenticated ();\n\n\t\t\tif ((engine.Capabilities & ImapCapabilities.Idle) == 0)\n\t\t\t\tthrow new NotSupportedException (\"The IMAP server does not support the IDLE extension.\");\n\n\t\t\tif (engine.State != ImapEngineState.Selected)\n\t\t\t\tthrow new InvalidOperationException (\"An ImapFolder has not been opened.\");\n\t\t}\n\n\t\tImapCommand QueueIdleCommand (ImapIdleContext context, CancellationToken cancellationToken)\n\t\t{\n\t\t\tvar ic = engine.QueueCommand (cancellationToken, null, \"IDLE\\r\\n\");\n\t\t\tic.ContinuationHandler = context.ContinuationHandler;\n\t\t\tic.UserData = context;\n","sourceCodeStart":1897,"sourceCodeEnd":1933,"githubUrl":"https://github.com/jstedfast/MailKit/blob/9d3859a7855e3e17582c07fd01972b8e262bf176/MailKit/Net/Imap/ImapClient.cs#L1897-L1933","documentation":"ImapClient.IdleAsync (via CheckCanIdle) requires the doneToken CancellationToken to be cancellable so the IDLE session can be terminated. A CancellationToken.None or default token can never be canceled, which would leave the client stuck in IDLE forever.","triggerScenarios":"Calling IdleAsync(folder, CancellationToken.None) or passing `default`; constructing a token via `new CancellationToken()` without any source.","commonSituations":"Code generated by passing CancellationToken.None everywhere as a placeholder; unit tests that use a default token; misunderstanding that doneToken is optional like a normal cancellationToken.","solutions":["Create a real token: var cts = new CancellationTokenSource(); and pass cts.Token","Cancel the token (cts.Cancel()) to end the IDLE session when done","Pass an existing application-level shutdown token instead of CancellationToken.None"],"exampleFix":"// before\nawait client.IdleAsync(folder, CancellationToken.None);\n// after\nusing var cts = new CancellationTokenSource(TimeSpan.FromMinutes(9));\nawait client.IdleAsync(folder, cts.Token);","handlingStrategy":"validation","validationCode":"if (!doneToken.CanBeCanceled)\n    throw new ArgumentException(\"doneToken must be cancellable\");","typeGuard":null,"tryCatchPattern":"try { await folder.IdleAsync(doneToken); }\ncatch (ArgumentException) { /* pass a real CTS token and retry */ }","preventionTips":["Never pass CancellationToken.None as a doneToken","Tie the idle loop to an application-shutdown CancellationTokenSource","Cancel the token (or use CancelAfter) to bound each IDLE session"],"tags":["imap","idle","cancellation","argument","mailkit"],"backgroundTag":"invalid-argument-value","analyzedSha":"9d3859a7855e3e17582c07fd01972b8e262bf176","analyzedAt":"2026-09-15T15:46:11.592Z","contentChangedAt":"2026-09-15T15:46:11.592Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}