Skip to content

imaplib.Idler raises IndexError when another command's tagged response arrives during IDLE #159583

Description

@MrGreyfun

Bug report

imaplib.IMAP4.idle() iteration raises an unhandled IndexError: pop from empty list when a tagged response arrives while the client is iterating over an IDLE session.

In Idler._pop() (Lib/imaplib.py, line 2001), after calling _get_response(timeout) the method unconditionally executes imap._idle_responses.pop(0). However, _get_response() handles a tagged response by storing it in self.tagged_commands only — it appends nothing to the _idle_responses queue. The queue is therefore still empty and pop(0) raises IndexError, which escapes into user code iterating the Idler.

This happens when a tagged reply for an earlier pipelined command arrives after the IDLE continuation (+) has been consumed and before the next untagged response. RFC 3501 section 5.5 permits command pipelining for IMAP4rev1. The tagged response below belongs to NOOP; the IDLE command's tagged completion still follows DONE as required by RFC 2177 section 3.

Reproducer (run with a CPython build from this checkout, e.g. PCbuild/amd64/python_d.exe repro.py):

import socketserver
import threading
import traceback

import imaplib


class IdleTaggedHandler(socketserver.StreamRequestHandler):
    def _send_line(self, line):
        if isinstance(line, str):
            line = line.encode('ascii')
        self.wfile.write(line + b'\r\n')

    def _send_tagged(self, tag, code, text):
        self._send_line(tag + b' ' + code.encode() + b' ' + text.encode())

    def handle(self):
        self._send_line('* OK [CAPABILITY IMAP4rev1 IDLE] ready')
        pending_noop = None
        while True:
            line = self.rfile.readline()
            if not line:
                return
            tag, _, rest = line.partition(b' ')
            cmd = rest.strip().split(b' ')[0].upper()
            if cmd == b'LOGIN':
                self._send_line(b'* CAPABILITY IMAP4rev1 IDLE')
                self._send_tagged(tag, 'OK', 'LOGIN completed')
            elif cmd == b'NOOP':
                pending_noop = tag
            elif cmd == b'CAPABILITY':
                self._send_line(b'* CAPABILITY IMAP4rev1 IDLE')
                self._send_tagged(tag, 'OK', 'CAPABILITY completed')
            elif cmd == b'IDLE':
                self._send_line(b'+ idling')
                self._send_tagged(pending_noop, 'OK', 'NOOP completed')
                self._send_line(b'* 1 EXISTS')
                line = self.rfile.readline()            # wait for DONE
                if line == b'DONE\r\n':
                    self._send_tagged(tag, 'OK', 'Idle completed')
                else:
                    self._send_tagged(tag, 'BAD', 'Expected DONE')
            else:
                self._send_tagged(tag, 'OK', 'done')


server = socketserver.TCPServer(('127.0.0.1', 0), IdleTaggedHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()

client = imaplib.IMAP4(*server.server_address)
client.login('user', 'pass')
# Leave NOOP's tagged completion pending while starting IDLE.
noop_tag = client._command('NOOP')
try:
    with client.idle() as idler:
        print(next(idler))           # <-- IndexError raised here
    print(client._get_tagged_response(noop_tag))
except Exception:
    traceback.print_exc()

Output on current main (d3ada45):

Traceback (most recent call last):
  File "repro.py", line 56, in <module>
    print(next(idler))
  File "Lib/imaplib.py", line 2014, in __next__
    typ, data = self._pop(timeout)
  File "Lib/imaplib.py", line 2001, in _pop
    resp = imap._idle_responses.pop(0)
IndexError: pop from empty list

Expected behavior: _get_response() should retain the tagged reply in tagged_commands, then IDLE iteration should continue until it can return the next untagged response. Any timeout must continue to use the original deadline.

Reproduced on CPython 3.16.0a1+dev, revision d3ada4509ea, on Windows x64. The reproduction uses the private _command() method to leave a tagged command response pending; the regular public command methods wait for their tagged response before returning.

CPython versions tested on:

3.16 (CPython 3.16.0a1+dev, revision d3ada4509ea)

Operating systems tested on:

Windows (x64)

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 16, 2026
  2. picnixz commented on Oct 16, 2026

    @picnixz
    Member

    cc @foresto I think you implemented the idler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions