Skip to content

fix(coding integrations): add catching for integration not found error#111691

Merged
sehr-m merged 10 commits intomasterfrom
sehrm/fix/clear-handoff-for-deleted-integration
Apr 1, 2026
Merged

fix(coding integrations): add catching for integration not found error#111691
sehr-m merged 10 commits intomasterfrom
sehrm/fix/clear-handoff-for-deleted-integration

Conversation

@sehr-m
Copy link
Copy Markdown
Member

@sehr-m sehr-m commented Mar 26, 2026

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.

@sehr-m sehr-m requested a review from a team as a code owner March 26, 2026 22:29
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Mar 26, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Create PR

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.

Comment thread src/sentry/seer/autofix/on_completion_hook.py Outdated
Comment thread src/sentry/seer/endpoints/seer_rpc.py
@sehr-m sehr-m marked this pull request as draft March 26, 2026 23:07
@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 654b1b8 in this run:

tests/sentry/seer/autofix/test_on_completion_hook.py::TestTriggerCodingAgentHandoff::test_not_found_clears_automation_handofflog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/autofix/test_on_completion_hook.py:43: in test_not_found_clears_automation_handoff
    AutofixOnCompletionHook._trigger_coding_agent_handoff(
E   TypeError: AutofixOnCompletionHook._trigger_coding_agent_handoff() got an unexpected keyword argument 'group_id'
tests/sentry/seer/autofix/test_on_completion_hook.py::TestTriggerCodingAgentHandoff::test_not_found_no_preference_response_does_not_call_setlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/autofix/test_on_completion_hook.py:65: in test_not_found_no_preference_response_does_not_call_set
    AutofixOnCompletionHook._trigger_coding_agent_handoff(
E   TypeError: AutofixOnCompletionHook._trigger_coding_agent_handoff() got an unexpected keyword argument 'group_id'
tests/sentry/seer/autofix/test_autofix_on_completion_hook.py::TestAutofixOnCompletionHookHandoff::test_trigger_coding_agent_handoff_clears_preference_on_not_foundlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/autofix/test_autofix_on_completion_hook.py:624: in test_trigger_coding_agent_handoff_clears_preference_on_not_found
    AutofixOnCompletionHook._trigger_coding_agent_handoff(
E   TypeError: AutofixOnCompletionHook._trigger_coding_agent_handoff() got an unexpected keyword argument 'group_id'
tests/sentry/seer/autofix/test_autofix_on_completion_hook.py::TestAutofixOnCompletionHookHandoff::test_trigger_coding_agent_handoff_not_found_seer_api_error_does_not_raiselog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/autofix/test_autofix_on_completion_hook.py:651: in test_trigger_coding_agent_handoff_not_found_seer_api_error_does_not_raise
    AutofixOnCompletionHook._trigger_coding_agent_handoff(
E   TypeError: AutofixOnCompletionHook._trigger_coding_agent_handoff() got an unexpected keyword argument 'group_id'

@sehr-m sehr-m marked this pull request as ready for review March 31, 2026 16:24
Comment thread src/sentry/seer/endpoints/seer_rpc.py Outdated
Comment thread src/sentry/seer/autofix/on_completion_hook.py Outdated
Comment thread src/sentry/seer/autofix/on_completion_hook.py
Comment thread tests/sentry/seer/autofix/test_autofix_on_completion_hook.py
@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 580e7e0 in this run:

tests/sentry/seer/autofix/test_autofix_on_completion_hook.py::TestAutofixOnCompletionHookHandoff::test_trigger_coding_agent_handoff_clears_preference_on_not_foundlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/autofix/test_autofix_on_completion_hook.py:631: in test_trigger_coding_agent_handoff_clears_preference_on_not_found
    mock_set_pref.assert_called_once()
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:956: in assert_called_once
    raise AssertionError(msg)
E   AssertionError: Expected 'set_project_seer_preference' to have been called once. Called 0 times.

Comment thread src/sentry/seer/endpoints/seer_rpc.py
Comment thread src/sentry/seer/endpoints/seer_rpc.py Outdated
Comment on lines +615 to +616
try:
project = Project.objects.get_from_cache(id=project_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

@JoshFerge JoshFerge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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"}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

@sehr-m sehr-m merged commit 0672c10 into master Apr 1, 2026
67 checks passed
@sehr-m sehr-m deleted the sehrm/fix/clear-handoff-for-deleted-integration branch April 1, 2026 16:47
Copy link
Copy Markdown
Member

@srest2021 srest2021 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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",
Copy link
Copy Markdown
Member

@srest2021 srest2021 Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make this a bit more distinguishable from other logs:

Suggested change
"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])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +500 to +501
resolved_pref = resolve_repository_ids(organization.id, [validated_pref])
write_preference_to_sentry_db(project, resolved_pref[0])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit

Suggested change
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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants