From 25259e89873d51a39767d882507865d6032effd5 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 14 Oct 2025 21:30:14 -0500 Subject: [PATCH 1/4] feat(backend): Add Sentry user and tag tracking to node execution Integrates Sentry SDK to set user and contextual tags during node execution for improved error tracking and user count analytics. Ensures Sentry context is properly set and restored, and exceptions are captured with relevant context before scope restoration. --- .../backend/backend/executor/manager.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index dea8cffd309a..e9cc77a101a4 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast +import sentry_sdk from pika.adapters.blocking_connection import BlockingChannel from pika.spec import Basic, BasicProperties from prometheus_client import Gauge, start_http_server @@ -224,6 +225,25 @@ async def execute_node( extra_exec_kwargs[field_name] = credentials output_size = 0 + + # sentry tracking nonsense to get user counts for blocks because isolation scopes don't work :( + scope = sentry_sdk.get_current_scope() + + # save the tags + original_user = scope._user + original_tags = dict(scope._tags) if scope._tags else {} + # Set user ID for error tracking + scope.set_user({"id": user_id}) + + scope.set_tag("graph_id", graph_id) + scope.set_tag("node_id", node_id) + scope.set_tag("block_name", node_block.name) + scope.set_tag("block_id", node_block.id) + scope.set_tag("node_exec_id", node_exec_id) + scope.set_tag("graph_exec_id", graph_exec_id) + for k, v in (data.user_context or {}).model_dump().items(): + scope.set_tag(f"user_context.{k}", v) + try: async for output_name, output_data in node_block.execute( input_data, **extra_exec_kwargs @@ -232,6 +252,12 @@ async def execute_node( output_size += len(json.dumps(output_data)) log_metadata.debug("Node produced output", **{output_name: output_data}) yield output_name, output_data + except Exception: + # Capture exception WITH context still set before restoring scope + sentry_sdk.capture_exception(scope=scope) + sentry_sdk.flush() # Ensure it's sent before we restore scope + # Re-raise to maintain normal error flow + raise finally: # Ensure credentials are released even if execution fails if creds_lock and (await creds_lock.locked()) and (await creds_lock.owned()): @@ -246,6 +272,10 @@ async def execute_node( execution_stats.input_size = input_size execution_stats.output_size = output_size + # Restore scope AFTER error has been captured + scope._user = original_user + scope._tags = original_tags + async def _enqueue_next_nodes( db_client: "DatabaseManagerAsyncClient", @@ -570,7 +600,6 @@ async def persist_output(output_name: str, output_data: Any) -> None: await persist_output( "error", str(stats.error) or type(stats.error).__name__ ) - return status @func_retry From 281e10715a23328a93850c1e9f4b8fea5be77bb8 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 14 Oct 2025 22:14:51 -0500 Subject: [PATCH 2/4] drop exec ids --- autogpt_platform/backend/backend/executor/manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index e9cc77a101a4..b2f82522651c 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -239,8 +239,6 @@ async def execute_node( scope.set_tag("node_id", node_id) scope.set_tag("block_name", node_block.name) scope.set_tag("block_id", node_block.id) - scope.set_tag("node_exec_id", node_exec_id) - scope.set_tag("graph_exec_id", graph_exec_id) for k, v in (data.user_context or {}).model_dump().items(): scope.set_tag(f"user_context.{k}", v) From e8a2b6ae1595ccdbb72d6e5c02a55a96b02c9541 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 14 Oct 2025 22:42:22 -0500 Subject: [PATCH 3/4] feat(backend): also track launchdarkly --- autogpt_platform/backend/backend/util/feature_flag.py | 5 +++++ autogpt_platform/backend/backend/util/metrics.py | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/autogpt_platform/backend/backend/util/feature_flag.py b/autogpt_platform/backend/backend/util/feature_flag.py index 8f1bb476bc43..6a0af88b1d96 100644 --- a/autogpt_platform/backend/backend/util/feature_flag.py +++ b/autogpt_platform/backend/backend/util/feature_flag.py @@ -37,6 +37,11 @@ class Flag(str, Enum): AGENT_ACTIVITY = "agent-activity" +def is_configured() -> bool: + """Check if LaunchDarkly is configured with an SDK key.""" + return bool(settings.secrets.launch_darkly_sdk_key) + + def get_client() -> LDClient: """Get the LaunchDarkly client singleton.""" if not _is_initialized: diff --git a/autogpt_platform/backend/backend/util/metrics.py b/autogpt_platform/backend/backend/util/metrics.py index 820d7a13f3fb..7922fef516ec 100644 --- a/autogpt_platform/backend/backend/util/metrics.py +++ b/autogpt_platform/backend/backend/util/metrics.py @@ -5,8 +5,10 @@ from pydantic import SecretStr from sentry_sdk.integrations.anthropic import AnthropicIntegration from sentry_sdk.integrations.asyncio import AsyncioIntegration +from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration from sentry_sdk.integrations.logging import LoggingIntegration +from backend.util.feature_flag import get_client, is_configured from backend.util.settings import Settings settings = Settings() @@ -19,6 +21,9 @@ class DiscordChannel(str, Enum): def sentry_init(): sentry_dsn = settings.secrets.sentry_dsn + integrations = [] + if is_configured(): + integrations.append(LaunchDarklyIntegration(get_client())) sentry_sdk.init( dsn=sentry_dsn, traces_sample_rate=1.0, @@ -31,7 +36,8 @@ def sentry_init(): AnthropicIntegration( include_prompts=False, ), - ], + ] + + integrations, ) From 166c53ac35c33d482439de785a5b37fd08c4ee7a Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Tue, 14 Oct 2025 22:46:05 -0500 Subject: [PATCH 4/4] Set default UserContext timezone to UTC in execute_node Ensures that if user_context is None, a default UserContext with timezone set to 'UTC' is used when setting Sentry scope tags in execute_node. --- autogpt_platform/backend/backend/executor/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index b2f82522651c..1628d19f4475 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -239,7 +239,7 @@ async def execute_node( scope.set_tag("node_id", node_id) scope.set_tag("block_name", node_block.name) scope.set_tag("block_id", node_block.id) - for k, v in (data.user_context or {}).model_dump().items(): + for k, v in (data.user_context or UserContext(timezone="UTC")).model_dump().items(): scope.set_tag(f"user_context.{k}", v) try: