Skip to content

fix: catch and retry on ConnectionError/Timeout from niquests (issue #647)#648

Draft
tobixen wants to merge 1 commit intomasterfrom
issue647
Draft

fix: catch and retry on ConnectionError/Timeout from niquests (issue #647)#648
tobixen wants to merge 1 commit intomasterfrom
issue647

Conversation

@tobixen
Copy link
Member

@tobixen tobixen commented Mar 21, 2026

This commit was done by Claude and hasn't been thoroughly reviewed yet. The code should be safe to run, but I'm wondering if this is the right way of fixing the problem. Pull request is in "draft mode" as for now.


niquests uses lazy HTTP/2 response gathering: accessing r.status_code or
r.headers can raise ConnectionError if the underlying connection has gone
stale. This produces a confusing nested-exception traceback and an
intermittent failure for callers (c.events(), etc.).

Changes:

  • Add DAVNetworkError(DAVError) to caldav/lib/error.py to represent
    network-level failures wrapping the underlying library exception.
  • In DAVClient._sync_request(), wrap the session.request() call and all
    subsequent response-attribute access in a try/except that catches
    requests.exceptions.ConnectionError and requests.exceptions.Timeout,
    re-raising as DAVNetworkError.
  • In DAVClient.request(), catch DAVNetworkError and retry once for
    idempotent methods (GET, HEAD, OPTIONS, PROPFIND, REPORT, PUT, DELETE,
    MKCOL, MKCALENDAR). POST is the only non-idempotent method and is not
    retried.
  • Apply the same pattern to AsyncDAVClient._async_request() using an
    outer try/except around the existing auth-workaround try/except, with
    a _connection_retried flag to prevent infinite loops.

The retry transparently recovers from stale HTTP/2 connection reuse, which
is the root cause of the "sometimes works, sometimes times out" symptom.

Ref: #647

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

…647)

niquests uses lazy HTTP/2 response gathering: accessing r.status_code or
r.headers can raise ConnectionError if the underlying connection has gone
stale.  This produces a confusing nested-exception traceback and an
intermittent failure for callers (c.events(), etc.).

Changes:
- Add DAVNetworkError(DAVError) to caldav/lib/error.py to represent
  network-level failures wrapping the underlying library exception.
- In DAVClient._sync_request(), wrap the session.request() call and all
  subsequent response-attribute access in a try/except that catches
  requests.exceptions.ConnectionError and requests.exceptions.Timeout,
  re-raising as DAVNetworkError.
- In DAVClient.request(), catch DAVNetworkError and retry once for
  idempotent methods (GET, HEAD, OPTIONS, PROPFIND, REPORT, PUT, DELETE,
  MKCOL, MKCALENDAR).  POST is the only non-idempotent method and is not
  retried.
- Apply the same pattern to AsyncDAVClient._async_request() using an
  outer try/except around the existing auth-workaround try/except, with
  a _connection_retried flag to prevent infinite loops.

The retry transparently recovers from stale HTTP/2 connection reuse, which
is the root cause of the "sometimes works, sometimes times out" symptom.

Ref: #647

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant