mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
## Why
Multiple Sentry issues paging on-call in prod:
1. **AUTOGPT-SERVER-8BP**: `ConversionError: Failed to convert
anthropic/claude-sonnet-4-6 to <enum 'LlmModel'>` — the copilot passes
OpenRouter-style provider-prefixed model names
(`anthropic/claude-sonnet-4-6`) to blocks, but the `LlmModel` enum only
recognizes the bare model ID (`claude-sonnet-4-6`).
2. **BUILDER-7GF**: `Error invoking postEvent: Method not found` —
Sentry SDK internal error on Chrome Mobile Android, not a platform bug.
3. **XMLParserBlock**: `BlockUnknownError raised by XMLParserBlock with
message: Error in input xml syntax` — user sent bad XML but the block
raised `SyntaxError`, which gets wrapped as `BlockUnknownError`
(unexpected) instead of `BlockExecutionError` (expected).
4. **AUTOGPT-SERVER-8BS**: `Virus scanning failed for Screenshot
2026-03-26 091900.png: range() arg 3 must not be zero` — empty (0-byte)
file upload causes `range(0, 0, 0)` in the virus scanner chunking loop,
and the failure is logged at `error` level which pages on-call.
5. **AUTOGPT-SERVER-8BT**: `ValueError: <Token var=<ContextVar
name='current_context'>> was created in a different Context` —
OpenTelemetry `context.detach()` fails when the SDK streaming async
generator is garbage-collected in a different context than where it was
created (client disconnect mid-stream).
6. **AUTOGPT-SERVER-8BW**: `RuntimeError: Attempted to exit cancel scope
in a different task than it was entered in` — anyio's
`TaskGroup.__aexit__` detects cancel scope entered in one task but
exited in another when `GeneratorExit` interrupts the SDK cleanup during
client disconnect.
7. **Workspace UniqueViolationError**: `UniqueViolationError: Unique
constraint failed on (workspaceId, path)` — race condition during
concurrent file uploads handled by `WorkspaceManager._persist_db_record`
retry logic, but Sentry still captures the exception at the raise site.
8. **Library UniqueViolationError**: `UniqueViolationError` on
`LibraryAgent (userId, agentGraphId, agentGraphVersion)` — race
conditions in `add_graph_to_library` and `create_library_agent` caused
crashes or silent data loss.
9. **Graph version collision**: `UniqueViolationError` on `AgentGraph
(id, version)` — copilot re-saving an agent at an existing version
collides with the primary key.
## What
### Backend: `LlmModel._missing_()` for provider-prefixed model names
- Adds `_missing_` classmethod to `LlmModel` enum that strips the
provider prefix (e.g., `anthropic/`) when direct lookup fails
- Self-contained in the enum — no changes to the generic type conversion
system
### Frontend: Filter Sentry SDK noise
- Adds `postEvent: Method not found` to `ignoreErrors` — a known Sentry
SDK issue on certain mobile browsers
### Backend: XMLParserBlock — raise ValueError instead of SyntaxError
- Changed `_validate_tokens()` to raise `ValueError` instead of
`SyntaxError`
- Changed the `except SyntaxError` handler in `run()` to re-raise as
`ValueError`
- This ensures `Block.execute()` wraps XML parsing failures as
`BlockExecutionError` (expected/user-caused) instead of
`BlockUnknownError` (unexpected/alerts Sentry)
### Backend: Virus scanner — handle empty files + reduce alert noise
- Added early return for empty (0-byte) files in `scan_file()` to avoid
`range() arg 3 must not be zero` when `chunk_size` is 0
- Added `max(1, len(content))` guard on `chunk_size` as defense-in-depth
- Downgraded `scan_content_safe` failure log from `error` to `warning`
so single-file scan failures don't page on-call via Sentry
### Backend: Suppress SDK client cleanup errors on SSE disconnect
- Replaced `async with ClaudeSDKClient` in `_run_stream_attempt` with
manual `__aenter__`/`__aexit__` wrapped in new
`_safe_close_sdk_client()` helper
- `_safe_close_sdk_client()` catches `ValueError` (OTEL context token
mismatch) and `RuntimeError` (anyio cancel scope in wrong task) during
`__aexit__` and logs at `debug` level — these are expected when SSE
client disconnects mid-stream
- Added `_is_sdk_disconnect_error()` helper for defense-in-depth at the
outer `except BaseException` handler in `stream_chat_completion_sdk`
- Both Sentry errors (8BT and 8BW) are now suppressed without affecting
normal cleanup flow
### Backend: Filter workspace UniqueViolationError from Sentry alerts
- Added `before_send` filter in `_before_send()` to drop
`UniqueViolationError` events where the message contains `workspaceId`
and `path`
- The error is already handled by `WorkspaceManager._persist_db_record`
retry logic — it must propagate for the retry logic to work, so the fix
is at the Sentry filter level rather than catching/suppressing at source
### Backend: Library agent race condition fixes
- **`add_graph_to_library`**: Replaced check-then-create pattern with
create-then-catch-`UniqueViolationError`-then-update. On collision,
updates the existing row (restoring soft-deleted/archived agents)
instead of crashing.
- **`create_library_agent`**: Replaced `create` with `upsert` on the
`(userId, agentGraphId, agentGraphVersion)` composite unique constraint,
so concurrent adds restore soft-deleted entries instead of throwing.
### Backend: Graph version auto-increment on collision
- `__create_graph` now checks if the `(id, version)` already exists
before `create_many`, and auto-increments the version to `max_existing +
1` to avoid `UniqueViolationError` when the copilot re-saves an agent.
### Backend: Workspace `get_or_create_workspace` upsert
- Changed from find-then-create to `upsert` to atomically handle
concurrent workspace creation.
## Test plan
- [x] `LlmModel("anthropic/claude-sonnet-4-6")` resolves correctly
- [x] `LlmModel("claude-sonnet-4-6")` still works (no regression)
- [x] `LlmModel("invalid/nonexistent-model")` still raises `ValueError`
- [x] XMLParserBlock: unclosed tags, extra closing tags, empty XML all
raise `ValueError`
- [x] XMLParserBlock: `SyntaxError` from gravitasml library is caught
and re-raised as `ValueError`
- [x] Virus scanner: empty file (0 bytes) returns clean without hitting
ClamAV
- [x] Virus scanner: single-byte file scans normally (regression test)
- [x] Virus scanner: `scan_content_safe` logs at WARNING not ERROR on
failure
- [x] SDK disconnect: `_is_sdk_disconnect_error` correctly identifies
cancel scope and context var errors
- [x] SDK disconnect: `_is_sdk_disconnect_error` rejects unrelated
errors
- [x] SDK disconnect: `_safe_close_sdk_client` suppresses ValueError,
RuntimeError, and unexpected exceptions
- [x] SDK disconnect: `_safe_close_sdk_client` calls `__aexit__` on
clean exit
- [x] Library: `add_graph_to_library` creates new agent on first call
- [x] Library: `add_graph_to_library` updates existing on
UniqueViolationError
- [x] Library: `create_library_agent` uses upsert to handle concurrent
adds
- [x] All existing workspace overwrite tests still pass
- [x] All tests passing (existing + 4 XML syntax + 3 virus scanner + 10
SDK disconnect + library tests)
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
// This file configures the initialization of Sentry on the client.
|
|
// The config you add here will be used whenever a users loads a page in their browser.
|
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
|
|
import { consent } from "@/services/consent/cookies";
|
|
import { environment } from "@/services/environment";
|
|
import * as Sentry from "@sentry/nextjs";
|
|
|
|
const isProdOrDev = environment.isProd() || environment.isDev();
|
|
const isCloud = environment.isCloud();
|
|
const isDisabled = process.env.DISABLE_SENTRY === "true";
|
|
|
|
const shouldEnable = !isDisabled && isProdOrDev && isCloud;
|
|
|
|
// Check for monitoring consent (includes session replay)
|
|
const hasMonitoringConsent = consent.hasConsentFor("monitoring");
|
|
|
|
Sentry.init({
|
|
dsn: "https://fe4e4aa4a283391808a5da396da20159@o4505260022104064.ingest.us.sentry.io/4507946746380288",
|
|
|
|
environment: environment.getEnvironmentStr(),
|
|
|
|
enabled: shouldEnable,
|
|
|
|
// Suppress cross-origin stylesheet errors from Sentry Replay (rrweb)
|
|
// serializing DOM snapshots with cross-origin stylesheets
|
|
// (e.g., from browser extensions or CDN-loaded CSS)
|
|
ignoreErrors: [
|
|
/Not allowed to access cross-origin stylesheet/,
|
|
// Sentry SDK internal issue on some mobile browsers
|
|
/Error invoking postEvent: Method not found/,
|
|
],
|
|
|
|
// Add optional integrations for additional features
|
|
integrations: [
|
|
Sentry.captureConsoleIntegration(),
|
|
Sentry.extraErrorDataIntegration(),
|
|
Sentry.browserProfilingIntegration(),
|
|
Sentry.httpClientIntegration(),
|
|
Sentry.launchDarklyIntegration(),
|
|
Sentry.replayIntegration({
|
|
unmask: [".sentry-unmask, [data-sentry-unmask]"],
|
|
}),
|
|
Sentry.replayCanvasIntegration(),
|
|
Sentry.reportingObserverIntegration(),
|
|
// Sentry.feedbackIntegration({
|
|
// // Additional SDK configuration goes in here, for example:
|
|
// colorScheme: "system",
|
|
// }),
|
|
],
|
|
|
|
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
|
tracesSampleRate: 1,
|
|
|
|
// Set `tracePropagationTargets` to control for which URLs trace propagation should be enabled
|
|
tracePropagationTargets: [
|
|
"localhost",
|
|
"localhost:8006",
|
|
/^https:\/\/dev\-builder\.agpt\.co\/api/,
|
|
/^https:\/\/.*\.agpt\.co\/api/,
|
|
],
|
|
|
|
// Define how likely Replay events are sampled.
|
|
// This sets the sample rate to be 10%. You may want this to be 100% while
|
|
// in development and sample at a lower rate in production
|
|
// GDPR: Only enable if user has consented to monitoring
|
|
replaysSessionSampleRate: hasMonitoringConsent ? 0.1 : 0,
|
|
|
|
// Define how likely Replay events are sampled when an error occurs.
|
|
// GDPR: Only enable if user has consented to monitoring
|
|
replaysOnErrorSampleRate: hasMonitoringConsent ? 1.0 : 0,
|
|
|
|
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
|
debug: false,
|
|
|
|
// Set profilesSampleRate to 1.0 to profile every transaction.
|
|
// Since profilesSampleRate is relative to tracesSampleRate,
|
|
// the final profiling rate can be computed as tracesSampleRate * profilesSampleRate
|
|
// For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would
|
|
// result in 25% of transactions being profiled (0.5*0.5=0.25)
|
|
profilesSampleRate: 1.0,
|
|
enableLogs: true,
|
|
});
|
|
|
|
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|