jstedfast/MailKit · error · InvalidOperationException

The IMAP command queue is empty.

Error message

The IMAP command queue is empty.

What it means

ImapEngine.PopNextCommand() throws this InvalidOperationException when it is asked to dequeue the next command to run but the internal command queue has zero entries. This is an internal synchronization invariant of the IMAP pipeline: a thread signaled the engine to run a command, but nothing was enqueued. It almost always indicates a race condition in caller thread usage of ImapClient.

Solutions

  1. Wrap every ImapClient call sequence in lock (imapClient.SyncRoot) { ... } so only one thread enqueues and processes commands at a time
  2. Use one thread/task per ImapClient, or create a separate ImapClient (and connection) per thread
  3. Check for double-consume bugs: ensure no code path signals/dispatches the queue more than once per enqueued command
  4. Upgrade MailKit - older versions had queue races; newer releases serialize via the engine task

Example fix

// before
await imapClient.Inbox.GetMessageAsync(id);
Task.Run(() => imapClient.Noop()); // unsynchronized

// after
lock (imapClient.SyncRoot) {
    imapClient.Noop();
}
await imapClient.Inbox.GetMessageAsync(id);
Defensive patterns

Strategy: try-catch

Validate before calling

if (queueIsEmptySignaled && !clientIsBusy) {
    // safe to proceed; otherwise synchronize first
}
lock (client.SyncRoot) {
    bool busy = client.IsBusy;
}

Type guard

bool CanIssueCommand(ImapClient client) {
    lock (client.SyncRoot) { return !client.IsBusy && client.IsConnected && client.IsAuthenticated; }
}

Try / catch

lock (client.SyncRoot) {
    try {
        // IMAP operations here
    } catch (InvalidOperationException ex) when (ex.Message.Contains("command queue is empty") || ex.Message.Contains("currently busy")) {
        // log and re-serialize access; do not retry blindly
    }
}

Prevention

When it happens

Trigger: Calling ImapClient send/authenticate/folder APIs concurrently from multiple threads without synchronizing on ImapClient.SyncRoot; calling methods after another thread has already drained the queue; mixing Sync and Async calls on the same client from different threads.

Common situations: Multi-threaded apps (e.g. background workers plus UI threads) sharing a single ImapClient instance; a keepalive/NOOP thread racing a fetch thread; incorrectly hand-rolled synchronization instead of locking SyncRoot.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15). Data as JSON: /api/errors/d066fdabf9d77c70. Report an issue: GitHub.

Appendix: source

Thrown at MailKit/Net/Imap/ImapEngine.cs:3133

					token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);
					AssertToken (token, ImapTokenType.Eoln, "Syntax error in untagged LIST response. {0}", token);
				} else if (atom.Equals ("VANISHED", StringComparison.OrdinalIgnoreCase) && folder != null) {
					await folder.OnVanishedAsync (this, cancellationToken).ConfigureAwait (false);
					await SkipLineAsync (cancellationToken).ConfigureAwait (false);
				} else {
					// don't know how to handle this... eat it?
					await SkipLineAsync (cancellationToken).ConfigureAwait (false);
				}
			}
		}

		[MemberNotNull (nameof (current))]
		void PopNextCommand ()
		{
			lock (queue) {
				if (queue.Count == 0)
					throw new InvalidOperationException ("The IMAP command queue is empty.");

				if (IsBusy)
					throw new InvalidOperationException ("The ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads.");

				current = queue[0];
				queue.RemoveAt (0);

				try {
					current.CancellationToken.ThrowIfCancellationRequested ();
				} catch {
					queue.RemoveAll (x => x.CancellationToken.IsCancellationRequested);
					current = null;
					throw;
				}
			}
		}

		/// <summary>

View on GitHub (pinned to 9d3859a785)