celery/celery · error · InvalidTaskError

invalid expires value {expires!r}: {exc}

Error message

invalid expires value {expires!r}: {exc}

What it means

Symmetric to eta: the message's expires field is parsed with maybe_iso8601; on AttributeError/ValueError/TypeError, Request raises InvalidTaskError('invalid expires value {expires!r}: {exc}'). expires may be a datetime or a seconds-int, but the ISO parse path fails on malformed values.

Source

Thrown at celery/worker/request.py:151

        # timezone means the message is timezone-aware, and the only timezone
        # supported at this point is UTC.
        eta = self._request_dict.get('eta')
        if eta is not None:
            try:
                eta = maybe_iso8601(eta)
            except (AttributeError, ValueError, TypeError) as exc:
                raise InvalidTaskError(
                    f'invalid ETA value {eta!r}: {exc}')
            self._eta = maybe_make_aware(eta, self.tzlocal)
        else:
            self._eta = None

        expires = self._request_dict.get('expires')
        if expires is not None:
            try:
                expires = maybe_iso8601(expires)
            except (AttributeError, ValueError, TypeError) as exc:
                raise InvalidTaskError(
                    f'invalid expires value {expires!r}: {exc}')
            self._expires = maybe_make_aware(expires, self.tzlocal)
        else:
            self._expires = None

        delivery_info = message.delivery_info or {}
        properties = message.properties or {}
        self._delivery_info = {
            'exchange': delivery_info.get('exchange'),
            'routing_key': delivery_info.get('routing_key'),
            'priority': properties.get('priority'),
            'redelivered': delivery_info.get('redelivered', False),
        }
        self._request_dict.update({
            'properties': properties,
            'reply_to': properties.get('reply_to'),
            'correlation_id': properties.get('correlation_id'),
            'hostname': self._hostname,

View on GitHub (pinned to 571efe8120)

Solutions

  1. Pass expires as an int/float number of seconds (relative) or a timezone-aware datetime / ISO-8601 string.
  2. Format datetimes with .isoformat() on the producer side.
  3. Validate the value type before sending and avoid locale-formatted strings.

Example fix

// before
task.apply_async(expires='in 5 minutes')  # InvalidTaskError

// after
task.apply_async(expires=300)  # 300 seconds
# or
task.apply_async(expires=datetime(2026,8,4,10,15,tzinfo=timezone.utc))
Defensive patterns

Strategy: validation

Validate before calling

from numbers import Real
from datetime import datetime

def valid_expires(value):
    if isinstance(value, Real) and not isinstance(value, bool):
        return float(value)
    if isinstance(value, datetime):
        return value
    raise ValueError(f'invalid expires value: {value!r}')

Type guard

from numbers import Real
from datetime import datetime

def is_valid_expires(v) -> bool:
    return (isinstance(v, Real) and not isinstance(v, bool)) or isinstance(v, datetime)

Try / catch

from celery.exceptions import InvalidTaskError
try:
    task.apply_async(expires=user_expires)
except InvalidTaskError as e:
    if 'invalid expires' in str(e):
        # fall back to a numeric seconds value
        ...
    else:
        raise

Prevention

When it happens

Trigger: A producer sends a task with apply_async(expires=...) as a malformed ISO string; passing a non-ISO/non-int value that reaches the ISO parser path.

Common situations: Confusing expires (relative seconds vs absolute datetime); passing a localized date string; a third-party producer with the wrong format; copying eta-style strings into expires.

Related errors


AI-assisted analysis of celery/celery@571efe8120 (2026-08-04). Data as JSON: /data/errors/bfd0b34620b6245a.json. Report an issue: GitHub.