fix: swallow the Okta SDK's None-response AttributeError as transient#546
Conversation
On some list/get endpoints the SDK dereferences response.status before checking the error, so a gateway failure that yields no response object raises AttributeError from inside the coroutine instead of returning it in the result tuple. _call now routes raised exceptions through _is_transient_okta_error, so this transient failure is swallowed by the syncer fan-out instead of surfacing as an unhandled error.
somethingnew2-0
left a comment
There was a problem hiding this comment.
Working this out with Claude, this Attribute error is not Transient and shows up on every staging run since #543 was deployed to staging
|
Sharing findings from investigating the same Root cause confirmed, plus a transport-layer detail. Agreed the SDK dereferences Why it surfaces now — the amplifier. In our case it correlates with running the syncer on a pooled client. Complement — a bounded retry (#547). This PR swallows and defers to the next reconcile. Since these are idempotent reads and an immediate retry gets a fresh connection, a single retry usually recovers within the same run instead of skipping the resource for a cycle. The SDK won't do it (it bails immediately on the null response and only retries HTTP statuses, not connection-launch failures). I put this in Connector-level hygiene — evaluated, not worth it. I checked whether tuning the pooled connector could prevent the stale reuse: Net: this PR (swallow) + #547 (retry) covers it well — nice work getting the reclassification in. 🤖 Posted by Claude Code |
The run-lifetime pooled aiohttp session the syncer uses reuses keep-alive connections Okta can close server-side; reusing a dead one raises a transient error the SDK does not retry itself (it bails immediately on a null response and only retries HTTP statuses). Retry a call that raises OktaTransientError once in _WrapperClient — an immediate retry gets a fresh connection and recovers within the run instead of skipping the resource until the next reconcile. Bounded to one retry so a degraded endpoint or a lingering 429 still gives up quickly. Composes with the None-response AttributeError reclassification in #546, which surfaces that specific transport failure as OktaTransientError for this retry to catch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Upstream fix submitted okta/okta-sdk-python#564 |
Summary
The syncer's fan-out logs an unhandled
AttributeError: 'NoneType' object has no attribute 'status'from the Okta SDK during upstream gateway blips. This routes that raised error through the existing transient-error handling so it is swallowed and retried on the next reconcile, matching how returned 5xx/timeout errors are already treated.Urgency: 3 days
Expected review effort: LOW
Motivation
Production error monitoring flagged an unhandled
AttributeErrororiginating in the Okta SDK's list/get endpoints. Root cause: the SDK dereferencesresponse.statusbefore checking the error, so when a gateway failure returns no response object (execute()returns(None, None, error)), the SDK raisesAttributeErrorfrom inside the coroutine instead of returning the error in its result tuple.The prior change that broadened transient handling to 5xx (#544) only inspects the returned error tuple in
_call, so it cannot catch a failure the SDK raises. The exception propagates out of_call, the syncer fan-out classifies it as a genericBaseException, and it is logged at ERROR level. This is the same class of transient upstream failureOktaTransientErroralready exists to absorb.Description of Changes
_callnow wrapsawait coroand routes raised exceptions through the existing_is_transient_okta_errorclassifier, so a transient failure that arrives as a raised exception is surfaced asOktaTransientError(same as one returned in the tuple)._is_transient_okta_errorrecognizes the SDK's None-responseAttributeErrorsignature (narrowly matched by message, so unrelatedAttributeErrors still propagate).Validation of Changes
AttributeError, confirmed it propagated unhandled before the fix, and that it now surfaces asOktaTransientError.AttributeErrorstill propagates.okta_service, transient-error, sync (membership/ownership/group/user), and fan-out suites pass;ruffandtyclean on changed files.Guidance for Reviewers
_is_transient_okta_erroris the main judgment call: aNone-response dereference in these SDK endpoints only happens on the transport/error path, so treating it as transient is safe for the read-only listing the syncer reconciles later. Confirm you are comfortable matching on the exception message rather than something more structural.