fix(coding integrations): add catching for integration not found error#111691
fix(coding integrations): add catching for integration not found error#111691
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Unhandled exceptions in NotFound recovery code can crash handler
- Wrapped preference-clearing logic in try-except to catch SeerApiError and SeerApiResponseValidationError, preventing uncaught exceptions from propagating.
- ✅ Fixed: Overly broad NotFound catch returns misleading error code
- Added error message inspection to only return integration_not_found error code when the NotFound exception is actually about an integration, preventing incorrect clearing of automation_handoff preference.
Or push these changes by commenting:
@cursor push 9001bd84c1
Preview (9001bd84c1)
diff --git a/src/sentry/seer/autofix/on_completion_hook.py b/src/sentry/seer/autofix/on_completion_hook.py
--- a/src/sentry/seer/autofix/on_completion_hook.py
+++ b/src/sentry/seer/autofix/on_completion_hook.py
@@ -531,12 +531,22 @@
"integration_id": handoff_config.integration_id,
},
)
- preference_response = get_project_seer_preferences(group.project_id)
- if preference_response and preference_response.preference:
- updated_preference = preference_response.preference.copy(
- update={"automation_handoff": None}
+ try:
+ preference_response = get_project_seer_preferences(group.project_id)
+ if preference_response and preference_response.preference:
+ updated_preference = preference_response.preference.copy(
+ update={"automation_handoff": None}
+ )
+ set_project_seer_preference(updated_preference)
+ except (SeerApiError, SeerApiResponseValidationError):
+ logger.exception(
+ "autofix.on_completion_hook.clear_handoff_preference_failed",
+ extra={
+ "run_id": run_id,
+ "organization_id": organization.id,
+ "project_id": group.project_id,
+ },
)
- set_project_seer_preference(updated_preference)
except Exception:
logger.exception(
"autofix.on_completion_hook.coding_agent_handoff_failed",
diff --git a/src/sentry/seer/endpoints/seer_rpc.py b/src/sentry/seer/endpoints/seer_rpc.py
--- a/src/sentry/seer/endpoints/seer_rpc.py
+++ b/src/sentry/seer/endpoints/seer_rpc.py
@@ -589,7 +589,7 @@
trigger_source=AutofixTriggerSource(trigger_source),
)
return {"success": True}
- except NotFound:
+ except NotFound as e:
logger.exception(
"coding_agent.rpc_launch_error",
extra={
@@ -598,7 +598,10 @@
"run_id": run_id,
},
)
- return {"success": False, "error_code": "integration_not_found"}
+ error_message = str(e)
+ if "Integration not found" in error_message:
+ return {"success": False, "error_code": "integration_not_found"}
+ return {"success": False}
except (PermissionDenied, ValidationError, APIException):
logger.exception(
"coding_agent.rpc_launch_error",This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
Backend Test FailuresFailures on
|
Backend Test FailuresFailures on
|
| try: | ||
| project = Project.objects.get_from_cache(id=project_id) |
There was a problem hiding this comment.
Bug: If project_id is None in trigger_coding_agent_launch, a call to Project.objects.get_from_cache fails silently, preventing the handoff preference from being cleared.
Severity: MEDIUM
Suggested Fix
Add a guard clause to check if project_id is not None before calling Project.objects.get_from_cache(id=project_id). The logic to clear the handoff preference should only be executed when a valid project_id is provided.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/seer/endpoints/seer_rpc.py#L615-L616
Potential issue: In the `trigger_coding_agent_launch` function, the `project_id`
parameter is optional and can be `None`. Within the `except IntegrationNotFound` block,
the code attempts to fetch a project using
`Project.objects.get_from_cache(id=project_id)`. If `project_id` is `None`, this call
raises an exception. This exception is then silently caught by a broad `except
Exception` block, which prevents the intended logic—clearing the user's handoff
preference from the Sentry database—from running. This silent failure undermines the
fix's goal of cleaning up stale integration references, which can occur if the calling
service sends a request without a `project_id`.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| "run_id": run_id, | ||
| }, | ||
| ) | ||
| return {"success": False, "error_code": "integration_not_found"} |
There was a problem hiding this comment.
Nullable project_id causes silent cleanup failure
Medium Severity
The project_id parameter defaults to None, but the IntegrationNotFound handler at line 616 calls Project.objects.get_from_cache(id=project_id) which will throw when project_id is None. The broad except Exception block silently swallows this error, meaning the Sentry DB preference cleanup — the core purpose of this fix — never executes. The function still returns the error_code, masking the fact that cleanup failed. Since this is an RPC endpoint with project_id newly added as optional, any caller that omits it will silently skip the stale preference cleanup.
srest2021
left a comment
There was a problem hiding this comment.
Lgtm, just some followups to think about in case you happen to touch this code again!
| write_preference_to_sentry_db(project, resolved_pref[0]) | ||
| except Exception: | ||
| logger.exception( | ||
| "seer.write_preferences.failed", |
There was a problem hiding this comment.
Just to make this a bit more distinguishable from other logs:
| "seer.write_preferences.failed", | |
| "seer.write_preferences.clear_handoff_preference_failed", |
| ) | ||
| validated_pref = SeerProjectPreference.validate(updated_preference) | ||
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | ||
| write_preference_to_sentry_db(project, resolved_pref[0]) |
There was a problem hiding this comment.
Same nit here, just moving [0] next to resolve_repository_ids
| ) | ||
| validated_pref = SeerProjectPreference.validate(updated_preference) | ||
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | ||
| write_preference_to_sentry_db(project, resolved_pref[0]) |
There was a problem hiding this comment.
Are we leaving the Seer DB pref cleanup on Seer side? Wondering why not reuse _clear_handoff_preference here, to guarantee we always hit the same code path for both DBs, and remove the Seer side cleanup.
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | ||
| write_preference_to_sentry_db(project, resolved_pref[0]) |
There was a problem hiding this comment.
Nit
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref]) | |
| write_preference_to_sentry_db(project, resolved_pref[0]) | |
| resolved_pref = resolve_repository_ids(organization.id, [validated_pref])[0] | |
| write_preference_to_sentry_db(project, resolved_pref) |



When a coding agent integration (Cursor, Claude Code) is deleted or marked PENDING_DELETION, Seer's stored automation_handoff preference still references the old integration_id. On every subsequent automated autofix run, Seer calls Sentry's trigger_coding_agent_launch RPC with the stale ID. Sentry validates the integration, finds it gone, and raises NotFound.
There are two paths that affect this, one is through the seer side root cause step and the other is after root cause completed first.
To fix the first, I pulled out the NotFound error when returning to Seer and used an error code to identify it. When this is caught we clear the handoff preferences. For the second, there has been a matching change for the calling path in a separate Seer PR.
Tests were added and I ran it locally to make sure it works.