Repository navigation
Conversation
The retry loop in _search guards the wait-and-sleep block with an inner "if request_attempts < max_retries" that merely repeats the while condition, and it is evaluated before the counter is incremented, so it is always true while the loop is running. The else branch that raises "Max API retries exceeded" is therefore dead code, and once the loop condition finally fails the function falls off the end and returns None. When Qintel is rate limiting (HTTP 429) or timing out (HTTP 504) long enough to exhaust the retries, every caller of _search -- search_pmi, search_qwatch, search_qauth, search_qsentry and qsentry_feed -- immediately calls .read() on that None and the analyst sees an opaque "'NoneType' object has no attribute 'read'" AttributeError instead of the rate-limit message. The redundant inner condition is dropped so the wait, log and sleep run on every retryable response, and the exception is raised after the loop exits, which is the only path left once the attempts are used up. Verified with py_compile on the changed file (CI excludes lib/ from flake8) and the full test suite against a live modules server: 161 passed, 4 skipped, 5 subtests passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dfYpyaSZd1nxSRLr8suj8
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF — Exhausted Qintel API retries return
Noneinstead of raising, leaving callers an opaque error._search(misp_modules/lib/qintel_helper.py) guards its raise with an innerif request_attempts < max_retriesthat duplicates thewhilecondition and is evaluated before the counter increments, so theelsebranch is dead and_searchfalls off the end returningNone.raise Exception("Max API retries exceeded")after the loop.AttributeErroronNoneType.read().The retry loop in
qintel_helper.py's_searchhad a dead error branch:The inner
if request_attempts < max_retriesduplicates thewhileloop's own guard and is evaluated beforerequest_attemptsis incremented, so it is always true inside the loop body — theelsebranch (and itsraise) can never execute. When retries are actually exhausted, thewhilecondition simply becomes false and_searchfalls off the end of the function, returningNoneinstead of raising.Impact: All five callers of
_search(search_pmi,search_qwatch,search_qauth,search_qsentry,qsentry_feed) immediately call.read()on the result. When the Qintel API rate-limits or times out repeatedly, the analyst doesn't see the intendedException("Max API retries exceeded")— they get an opaqueAttributeError: 'NoneType' object has no attribute 'read'deep in the module, with no indication that the real cause was exhausted retries against the Qintel API.Fix: Removed the redundant inner condition, dedented the wait/log/sleep block so it always runs inside the loop, and moved
raise Exception("Max API retries exceeded")to after thewhileloop, so it fires exactly when the loop exits because retries are exhausted. This is the only behavior change: on retry exhaustion the caller now gets the intended, informative exception instead of an unrelatedAttributeErrorseveral frames later.Verification
python -m py_compile misp_modules/lib/qintel_helper.py— clean. (This file lives undermisp_modules/lib/, which the repo's flake8 CI config excludes.)Found during a review of the repository; other findings are being submitted as separate PRs.
🤖 Generated with Claude Code