diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md
index 0ac58c1503ec..f4b4bd287271 100644
--- a/.cursor/BUGBOT.md
+++ b/.cursor/BUGBOT.md
@@ -58,6 +58,11 @@ Do not flag the issues below if they appear in tests.
- Flag usage of `expect.objectContaining` and other relaxed assertions, when a test expects something NOT to be included in a payload but there's no respective assertion.
- Flag usage of conditionals in one test and recommend splitting up the test for the different paths.
- Flag usage of loops testing multiple scenarios in one test and recommend using `(it)|(test).each` instead.
+- Flag tests that are likely to introduce flakes. In our case this usually means we wait for some telemetry requests sent from an SDK. Patterns to look out for:
+ - Only waiting for a request, after an action is performed. Instead, start waiting, perform action, await request promise.
+ - Race conditions when waiting on multiple requests. Ensure that waiting checks are unique enough and don't depend on a hard order when there's a chance that telemetry can be sent in arbitrary order.
+ - Timeouts or sleeps in tests. Instead suggest concrete events or other signals to wait on.
+- Flag usage of `getFirstEnvelope*`, `getMultipleEnvelope*` or related test helpers. These are NOT reliable anymore. Instead suggest helpers like `waitForTransaction`, `waitForError`, `waitForSpans`, etc.
## Platform-safe code
diff --git a/.github/FLAKY_CI_FAILURE_TEMPLATE.md b/.github/FLAKY_CI_FAILURE_TEMPLATE.md
new file mode 100644
index 000000000000..c105f6928d27
--- /dev/null
+++ b/.github/FLAKY_CI_FAILURE_TEMPLATE.md
@@ -0,0 +1,24 @@
+---
+title: '[Flaky CI]: {{ env.JOB_NAME }} - {{ env.TEST_NAME }}'
+labels: Tests
+---
+
+### Flakiness Type
+
+Other / Unknown
+
+### Name of Job
+
+{{ env.JOB_NAME }}
+
+### Name of Test
+
+{{ env.TEST_NAME }}
+
+### Link to Test Run
+
+{{ env.RUN_LINK }}
+
+---
+
+_This issue was automatically created._
diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml
index 8ca47ce04081..ec6ae171e925 100644
--- a/.github/actions/install-playwright/action.yml
+++ b/.github/actions/install-playwright/action.yml
@@ -18,7 +18,7 @@ runs:
working-directory: ${{ inputs.cwd }}
- name: Restore cached playwright binaries
- uses: actions/cache/restore@v4
+ uses: actions/cache/restore@v5
id: playwright-cache
with:
path: |
@@ -43,7 +43,7 @@ runs:
# Only store cache on develop branch
- name: Store cached playwright binaries
- uses: actions/cache/save@v4
+ uses: actions/cache/save@v5
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
with:
path: |
diff --git a/.github/actions/restore-cache/action.yml b/.github/actions/restore-cache/action.yml
index 7e7a3971cd7e..1d5126fbe952 100644
--- a/.github/actions/restore-cache/action.yml
+++ b/.github/actions/restore-cache/action.yml
@@ -11,13 +11,13 @@ runs:
steps:
- name: Check dependency cache
id: dep-cache
- uses: actions/cache/restore@v4
+ uses: actions/cache/restore@v5
with:
path: ${{ env.CACHED_DEPENDENCY_PATHS }}
key: ${{ inputs.dependency_cache_key }}
- name: Restore build artifacts
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v7
with:
name: build-output
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cf00b5d00435..544bb7900008 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -152,15 +152,15 @@ jobs:
changed_node:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry/node') }}
- changed_node_overhead_action:
- ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
- '@sentry-internal/node-overhead-gh-action') }}
changed_deno:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry/deno') }}
changed_bun:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry/bun') }}
+ changed_bun_integration:
+ ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
+ '@sentry-internal/bun-integration-tests') }}
changed_browser_integration:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry-internal/browser-integration-tests') }}
@@ -208,37 +208,6 @@ jobs:
# Only run comparison against develop if this is a PR
comparison_branch: ${{ (github.event_name == 'pull_request' && github.base_ref) || ''}}
- job_node_overhead_check:
- name: Node Overhead Check
- needs: [job_get_metadata, job_build]
- timeout-minutes: 15
- runs-on: ubuntu-24.04
- if:
- (needs.job_build.outputs.changed_node == 'true' && github.event_name == 'pull_request') ||
- (needs.job_build.outputs.changed_node_overhead_action == 'true' && github.event_name == 'pull_request') ||
- needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true'
- steps:
- - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
- uses: actions/checkout@v6
- with:
- ref: ${{ env.HEAD_COMMIT }}
- - name: Set up Node
- uses: actions/setup-node@v6
- with:
- node-version-file: 'package.json'
- - name: Restore caches
- uses: ./.github/actions/restore-cache
- with:
- dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
- - name: Check node overhead
- uses: ./dev-packages/node-overhead-gh-action
- env:
- DEBUG: '1'
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- # Only run comparison against develop if this is a PR
- comparison_branch: ${{ (github.event_name == 'pull_request' && github.base_ref) || ''}}
-
job_lint:
name: Lint
# Even though the linter only checks source code, not built code, it needs the built code in order check that all
@@ -605,6 +574,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
directory: dev-packages/browser-integration-tests
+ enable-coverage: false
name:
browser-playwright-${{ matrix.bundle }}-${{ matrix.project }}${{ matrix.shard && format('-{0}',
matrix.shard) || '' }}
@@ -669,6 +639,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
directory: dev-packages/browser-integration-tests
+ enable-coverage: false
name: browser-loader-${{ matrix.bundle }}
job_check_for_faulty_dts:
@@ -737,15 +708,6 @@ jobs:
working-directory: dev-packages/node-integration-tests
run: yarn test
- - name: Parse and Upload Coverage
- if: cancelled() == false
- continue-on-error: true
- uses: getsentry/codecov-action@main
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
- directory: dev-packages/node-integration-tests
- name: node-integration-${{ matrix.node }}${{ matrix.typescript && format('-ts{0}', matrix.typescript) || '' }}
-
job_node_core_integration_tests:
name:
Node (${{ matrix.node }})${{ (matrix.typescript && format(' (TS {0})', matrix.typescript)) || '' }} Node-Core
@@ -787,16 +749,6 @@ jobs:
working-directory: dev-packages/node-core-integration-tests
run: yarn test
- - name: Parse and Upload Coverage
- if: cancelled() == false
- continue-on-error: true
- uses: getsentry/codecov-action@main
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
- directory: dev-packages/node-core-integration-tests
- name:
- node-core-integration-${{ matrix.node }}${{ matrix.typescript && format('-ts{0}', matrix.typescript) || ''}}
-
job_cloudflare_integration_tests:
name: Cloudflare Integration Tests
needs: [job_get_metadata, job_build]
@@ -820,6 +772,32 @@ jobs:
working-directory: dev-packages/cloudflare-integration-tests
run: yarn test
+ job_bun_integration_tests:
+ name: Bun Integration Tests
+ needs: [job_get_metadata, job_build]
+ if: needs.job_build.outputs.changed_bun_integration == 'true' || github.event_name != 'pull_request'
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ steps:
+ - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ env.HEAD_COMMIT }}
+ - name: Set up Node
+ uses: actions/setup-node@v6
+ with:
+ node-version-file: 'package.json'
+ - name: Set up Bun
+ uses: oven-sh/setup-bun@v2
+ - name: Restore caches
+ uses: ./.github/actions/restore-cache
+ with:
+ dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
+
+ - name: Run integration tests
+ working-directory: dev-packages/bun-integration-tests
+ run: yarn test
+
job_remix_integration_tests:
name: Remix (Node ${{ matrix.node }}) Tests
needs: [job_get_metadata, job_build]
@@ -856,15 +834,6 @@ jobs:
cd packages/remix
yarn test:integration:ci
- - name: Parse and Upload Coverage
- if: cancelled() == false
- continue-on-error: true
- uses: getsentry/codecov-action@main
- with:
- directory: packages/remix
- token: ${{ secrets.GITHUB_TOKEN }}
- name: ${{ matrix.node }}
-
job_e2e_prepare:
name: Prepare E2E tests
# We want to run this if:
@@ -970,7 +939,7 @@ jobs:
use-installer: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Deno
- if: matrix.test-application == 'deno'
+ if: matrix.test-application == 'deno' || matrix.test-application == 'deno-streamed'
uses: denoland/setup-deno@v2.0.3
with:
deno-version: v2.1.5
@@ -990,11 +959,6 @@ jobs:
if: steps.restore-tarball-cache.outputs.cache-hit != 'true'
run: yarn build:tarball
- - name: Get node version
- id: versions
- run: |
- echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT
-
- name: Validate Verdaccio
run: yarn test:validate
working-directory: dev-packages/e2e-tests
@@ -1002,8 +966,6 @@ jobs:
- name: Prepare Verdaccio
run: yarn test:prepare
working-directory: dev-packages/e2e-tests
- env:
- E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}
- name: Copy to temp
run: yarn ci:copy-to-temp ./test-applications/${{ matrix.test-application }} ${{ runner.temp }}/test-application
@@ -1053,15 +1015,6 @@ jobs:
retention-days: 7
if-no-files-found: ignore
- - name: Parse and Upload Coverage
- if: cancelled() == false
- continue-on-error: true
- uses: getsentry/codecov-action@main
- with:
- directory: dev-packages/e2e-tests
- token: ${{ secrets.GITHUB_TOKEN }}
- name: e2e-${{ matrix.test-application }}
-
# - We skip optional tests on release branches
job_optional_e2e_tests:
name: E2E ${{ matrix.label || matrix.test-application }} Test (optional)
@@ -1116,11 +1069,6 @@ jobs:
if: steps.restore-tarball-cache.outputs.cache-hit != 'true'
run: yarn build:tarball
- - name: Get node version
- id: versions
- run: |
- echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT
-
- name: Validate Verdaccio
run: yarn test:validate
working-directory: dev-packages/e2e-tests
@@ -1128,8 +1076,6 @@ jobs:
- name: Prepare Verdaccio
run: yarn test:prepare
working-directory: dev-packages/e2e-tests
- env:
- E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}
- name: Copy to temp
run: yarn ci:copy-to-temp ./test-applications/${{ matrix.test-application }} ${{ runner.temp }}/test-application
@@ -1178,6 +1124,7 @@ jobs:
job_node_integration_tests,
job_node_core_integration_tests,
job_cloudflare_integration_tests,
+ job_bun_integration_tests,
job_browser_playwright_tests,
job_browser_loader_tests,
job_remix_integration_tests,
@@ -1192,7 +1139,28 @@ jobs:
# Always run this, even if a dependent job failed
if: always()
runs-on: ubuntu-24.04
+ permissions:
+ issues: write
+ checks: read
steps:
+ - name: Check out current commit
+ if: github.ref == 'refs/heads/develop' && contains(needs.*.result, 'failure')
+ uses: actions/checkout@v6
+ with:
+ sparse-checkout: |
+ .github
+ scripts
+
+ - name: Create issues for failed jobs
+ if: github.ref == 'refs/heads/develop' && contains(needs.*.result, 'failure')
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { default: run } = await import(
+ `${process.env.GITHUB_WORKSPACE}/scripts/report-ci-failures.mjs`
+ );
+ await run({ github, context, core });
+
- name: Check for failures
if: cancelled() || contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml
index f9c74b5f344f..ac4e1df08841 100644
--- a/.github/workflows/canary.yml
+++ b/.github/workflows/canary.yml
@@ -140,11 +140,6 @@ jobs:
path: ${{ env.CACHED_BUILD_PATHS }}
key: canary-${{ env.HEAD_COMMIT }}
- - name: Get node version
- id: versions
- run: |
- echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT
-
- name: Validate Verdaccio
run: yarn test:validate
working-directory: dev-packages/e2e-tests
@@ -152,8 +147,6 @@ jobs:
- name: Prepare Verdaccio
run: yarn test:prepare
working-directory: dev-packages/e2e-tests
- env:
- E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}
- name: Copy to temp
run: yarn ci:copy-to-temp ./test-applications/${{ matrix.test-application }} ${{ runner.temp }}/test-application
diff --git a/.github/workflows/changelog-preview.yml b/.github/workflows/changelog-preview.yml
deleted file mode 100644
index 9aabf51e1070..000000000000
--- a/.github/workflows/changelog-preview.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: Changelog Preview
-on:
- pull_request_target:
- types:
- - opened
- - synchronize
- - reopened
- - edited
- - labeled
- - unlabeled
-permissions:
- contents: write
- pull-requests: write
- statuses: write
-
-jobs:
- changelog-preview:
- uses: getsentry/craft/.github/workflows/changelog-preview.yml@2.25.2
- secrets: inherit
diff --git a/.github/workflows/ci-metadata.yml b/.github/workflows/ci-metadata.yml
index c4fca988d724..0f39590bf167 100644
--- a/.github/workflows/ci-metadata.yml
+++ b/.github/workflows/ci-metadata.yml
@@ -51,14 +51,17 @@ jobs:
id: get_metadata
# We need to try a number of different options for finding the head commit, because each kind of trigger event
# stores it in a different location
+ env:
+ COMMIT_SHA_EXPR:
+ ${{ github.event.pull_request.head.sha || github.event.head_commit.id || inputs.head_commit }}
run: |
- COMMIT_SHA=$(git rev-parse --short ${{ github.event.pull_request.head.sha || github.event.head_commit.id || inputs.head_commit }})
+ COMMIT_SHA=$(git rev-parse --short "$COMMIT_SHA_EXPR")
echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV
echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV
# Most changed packages are determined in job_build via Nx
- name: Determine changed packages
- uses: dorny/paths-filter@v3.0.1
+ uses: dorny/paths-filter@v4.0.1
id: changed
with:
filters: |
@@ -67,10 +70,6 @@ jobs:
any_code:
- '!**/*.md'
- - name: Get PR labels
- id: pr-labels
- uses: mydea/pr-labels-action@fn/bump-node20
-
outputs:
commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}'
# Note: These next three have to be checked as strings ('true'/'false')!
@@ -83,7 +82,8 @@ jobs:
# When merging into master, or from master
is_gitflow_sync: ${{ github.head_ref == 'master' || github.ref == 'refs/heads/master' }}
has_gitflow_label:
- ${{ github.event_name == 'pull_request' && contains(steps.pr-labels.outputs.labels, ' Gitflow ') }}
+ ${{ github.event_name == 'pull_request' && contains(toJSON(github.event.pull_request.labels.*.name), 'Gitflow')
+ }}
force_skip_cache:
${{ github.event_name == 'schedule' || (github.event_name == 'pull_request' &&
- contains(steps.pr-labels.outputs.labels, ' ci-skip-cache ')) }}
+ contains(toJSON(github.event.pull_request.labels.*.name), 'ci-skip-cache')) }}
diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml
index aa2c33336bd2..c0a8f1f720b1 100644
--- a/.github/workflows/flaky-test-detector.yml
+++ b/.github/workflows/flaky-test-detector.yml
@@ -55,7 +55,7 @@ jobs:
browsers: 'chromium'
- name: Determine changed tests
- uses: dorny/paths-filter@v3.0.1
+ uses: dorny/paths-filter@v4.0.1
id: changed
with:
list-files: json
diff --git a/.github/workflows/pr-review-reminder.yml b/.github/workflows/pr-review-reminder.yml
new file mode 100644
index 000000000000..3eda72221948
--- /dev/null
+++ b/.github/workflows/pr-review-reminder.yml
@@ -0,0 +1,39 @@
+name: 'PR: Review Reminder'
+
+on:
+ workflow_dispatch:
+ schedule:
+ # Run on weekdays at 10:00 AM UTC. No new reminders can fire on weekends because
+ # Saturday/Sunday are never counted as business days.
+ - cron: '0 10 * * 1-5'
+
+# pulls.* list + listRequestedReviewers → pull-requests: read
+# issues timeline + comments + createComment → issues: write
+# repos.listCollaborators (outside) → Metadata read on the token (see GitHub App permission map)
+# checkout → contents: read
+permissions:
+ contents: read
+ issues: write
+ pull-requests: read
+
+concurrency:
+ group: ${{ github.workflow }}
+ cancel-in-progress: false
+
+jobs:
+ remind-reviewers:
+ # `schedule` has no `repository` on github.event; forks must be skipped only for workflow_dispatch.
+ if: github.event_name == 'schedule' || github.event.repository.fork != true
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Remind pending reviewers
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { default: run } = await import(
+ `${process.env.GITHUB_WORKSPACE}/scripts/pr-review-reminder.mjs`
+ );
+ await run({ github, context, core });
diff --git a/.size-limit.js b/.size-limit.js
index 1e6e8d951464..86f3ef5ed87d 100644
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -38,21 +38,28 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
- limit: '43 KB',
+ limit: '44 KB',
+ },
+ {
+ name: '@sentry/browser (incl. Tracing + Span Streaming)',
+ path: 'packages/browser/build/npm/esm/prod/index.js',
+ import: createImport('init', 'browserTracingIntegration', 'spanStreamingIntegration'),
+ gzip: true,
+ limit: '48 KB',
},
{
name: '@sentry/browser (incl. Tracing, Profiling)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'browserProfilingIntegration'),
gzip: true,
- limit: '48 KB',
+ limit: '49 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
gzip: true,
- limit: '82 KB',
+ limit: '83 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',
@@ -82,14 +89,14 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'),
gzip: true,
- limit: '87 KB',
+ limit: '88 KB',
},
{
name: '@sentry/browser (incl. Tracing, Replay, Feedback)',
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'),
gzip: true,
- limit: '99 KB',
+ limit: '100 KB',
},
{
name: '@sentry/browser (incl. Feedback)',
@@ -163,7 +170,7 @@ module.exports = [
path: 'packages/vue/build/esm/index.js',
import: createImport('init', 'browserTracingIntegration'),
gzip: true,
- limit: '45 KB',
+ limit: '46 KB',
},
// Svelte SDK (ESM)
{
@@ -184,7 +191,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing)',
path: createCDNPath('bundle.tracing.min.js'),
gzip: true,
- limit: '44 KB',
+ limit: '45 KB',
},
{
name: 'CDN Bundle (incl. Logs, Metrics)',
@@ -196,7 +203,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Logs, Metrics)',
path: createCDNPath('bundle.tracing.logs.metrics.min.js'),
gzip: true,
- limit: '45 KB',
+ limit: '46 KB',
},
{
name: 'CDN Bundle (incl. Replay, Logs, Metrics)',
@@ -208,25 +215,25 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Replay)',
path: createCDNPath('bundle.tracing.replay.min.js'),
gzip: true,
- limit: '81 KB',
+ limit: '82 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)',
path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'),
gzip: true,
- limit: '82 KB',
+ limit: '83 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback)',
path: createCDNPath('bundle.tracing.replay.feedback.min.js'),
gzip: true,
- limit: '87 KB',
+ limit: '88 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)',
path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'),
gzip: true,
- limit: '88 KB',
+ limit: '89 KB',
},
// browser CDN bundles (non-gzipped)
{
@@ -234,14 +241,14 @@ module.exports = [
path: createCDNPath('bundle.min.js'),
gzip: false,
brotli: false,
- limit: '83 KB',
+ limit: '83.5 KB',
},
{
name: 'CDN Bundle (incl. Tracing) - uncompressed',
path: createCDNPath('bundle.tracing.min.js'),
gzip: false,
brotli: false,
- limit: '130 KB',
+ limit: '134 KB',
},
{
name: 'CDN Bundle (incl. Logs, Metrics) - uncompressed',
@@ -255,7 +262,7 @@ module.exports = [
path: createCDNPath('bundle.tracing.logs.metrics.min.js'),
gzip: false,
brotli: false,
- limit: '134 KB',
+ limit: '138 KB',
},
{
name: 'CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed',
@@ -269,14 +276,14 @@ module.exports = [
path: createCDNPath('bundle.tracing.replay.min.js'),
gzip: false,
brotli: false,
- limit: '247 KB',
+ limit: '251 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed',
path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'),
gzip: false,
brotli: false,
- limit: '250 KB',
+ limit: '255 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed',
@@ -290,7 +297,7 @@ module.exports = [
path: createCDNPath('bundle.tracing.replay.feedback.logs.metrics.min.js'),
gzip: false,
brotli: false,
- limit: '264 KB',
+ limit: '268 KB',
},
// Next.js SDK (ESM)
{
@@ -299,7 +306,7 @@ module.exports = [
import: createImport('init'),
ignore: ['next/router', 'next/constants'],
gzip: true,
- limit: '48 KB',
+ limit: '49 KB',
},
// SvelteKit SDK (ESM)
{
@@ -308,7 +315,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
- limit: '44 KB',
+ limit: '45 KB',
},
// Node-Core SDK (ESM)
{
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8141da2e6276..7ba7c69b14dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,104 @@
## Unreleased
+## 10.49.0
+
+### Important Changes
+
+- **feat(browser): Add View Hierarchy integration ([#14981](https://github.com/getsentry/sentry-javascript/pull/14981))**
+
+ A new `viewHierarchyIntegration` captures the DOM structure when an error occurs, providing a snapshot of the page state for debugging. Enable it in your Sentry configuration:
+
+ ```javascript
+ import * as Sentry from '@sentry/browser';
+
+ Sentry.init({
+ dsn: '__DSN__',
+ integrations: [Sentry.viewHierarchyIntegration()],
+ });
+ ```
+
+- **feat(cloudflare): Split alarms into multiple traces and link them ([#19373](https://github.com/getsentry/sentry-javascript/pull/19373))**
+
+ Durable Object alarms now create separate traces for each alarm invocation, with proper linking between related alarms for better observability.
+
+- **feat(cloudflare): Enable RPC trace propagation with `enableRpcTracePropagation` ([#19991](https://github.com/getsentry/sentry-javascript/pull/19991), [#20345](https://github.com/getsentry/sentry-javascript/pull/20345))**
+
+ A new `enableRpcTracePropagation` option enables automatic trace propagation for Cloudflare RPC calls via `.fetch()`, ensuring distributed traces flow correctly across service bindings.
+
+- **feat(core): Add `enableTruncation` option to AI integrations ([#20167](https://github.com/getsentry/sentry-javascript/pull/20167), [#20181](https://github.com/getsentry/sentry-javascript/pull/20181), [#20182](https://github.com/getsentry/sentry-javascript/pull/20182), [#20183](https://github.com/getsentry/sentry-javascript/pull/20183), [#20184](https://github.com/getsentry/sentry-javascript/pull/20184))**
+
+ All AI integrations (OpenAI, Anthropic, Google GenAI, LangChain, LangGraph) now support an `enableTruncation` option to control whether large AI inputs/outputs are truncated.
+
+- **feat(opentelemetry): Vendor `AsyncLocalStorageContextManager` ([#20243](https://github.com/getsentry/sentry-javascript/pull/20243))**
+
+ The OpenTelemetry context manager is now vendored internally, reducing external dependencies and ensuring consistent behavior across environments.
+
+### Other Changes
+
+- feat(core): Export a reusable function to add tracing headers ([#20076](https://github.com/getsentry/sentry-javascript/pull/20076))
+- feat(core): Expose `rewriteSources` top level option ([#20142](https://github.com/getsentry/sentry-javascript/pull/20142))
+- feat(deps): bump defu from 6.1.4 to 6.1.6 ([#20104](https://github.com/getsentry/sentry-javascript/pull/20104))
+- feat(node-native): Add support for V8 v14 (Node v25+) ([#20125](https://github.com/getsentry/sentry-javascript/pull/20125))
+- feat(node): Include global scope for `eventLoopBlockIntegration` ([#20108](https://github.com/getsentry/sentry-javascript/pull/20108))
+- fix(core, node): Support loading Express options lazily ([#20211](https://github.com/getsentry/sentry-javascript/pull/20211))
+- fix(core): Set `conversation_id` only on `gen_ai` spans ([#20274](https://github.com/getsentry/sentry-javascript/pull/20274))
+- fix(core): Use `ai.operationId` for Vercel AI V6 operation name mapping ([#20285](https://github.com/getsentry/sentry-javascript/pull/20285))
+- fix(deno): Avoid inferring invalid span op from Deno tracer ([#20128](https://github.com/getsentry/sentry-javascript/pull/20128))
+- fix(deno): Handle `reader.closed` rejection from `releaseLock()` in streaming ([#20187](https://github.com/getsentry/sentry-javascript/pull/20187))
+- fix(nextjs): Preserve directive prologues in turbopack loaders ([#20103](https://github.com/getsentry/sentry-javascript/pull/20103))
+- fix(nextjs): Skip custom browser tracing setup for bot user agents ([#20263](https://github.com/getsentry/sentry-javascript/pull/20263))
+- fix(opentelemetry): Use WeakRef for context stored on scope to prevent memory leak ([#20328](https://github.com/getsentry/sentry-javascript/pull/20328))
+- fix(replay): Use live click attributes in breadcrumbs ([#20262](https://github.com/getsentry/sentry-javascript/pull/20262))
+
+
+ Internal Changes
+
+- chore: Add PR review reminder workflow ([#20175](https://github.com/getsentry/sentry-javascript/pull/20175))
+- chore: Fix lint warnings ([#20250](https://github.com/getsentry/sentry-javascript/pull/20250))
+- chore(bugbot): Add rules to flag test-flake-provoking patterns ([#20192](https://github.com/getsentry/sentry-javascript/pull/20192))
+- chore(ci): Bump actions/cache to v5 and actions/download-artifact to v7 ([#20249](https://github.com/getsentry/sentry-javascript/pull/20249))
+- chore(ci): Bump dorny/paths-filter from v3.0.1 to v4.0.1 ([#20251](https://github.com/getsentry/sentry-javascript/pull/20251))
+- chore(ci): Remove codecov steps from jobs that produce no coverage/JUnit data ([#20244](https://github.com/getsentry/sentry-javascript/pull/20244))
+- chore(ci): Remove craft changelog preview ([#20271](https://github.com/getsentry/sentry-javascript/pull/20271))
+- chore(ci): Remove node-overhead GitHub Action ([#20246](https://github.com/getsentry/sentry-javascript/pull/20246))
+- chore(ci): Replace pr-labels-action with native GitHub expressions ([#20252](https://github.com/getsentry/sentry-javascript/pull/20252))
+- chore(ci): Skip flaky issue creation for optional tests ([#20288](https://github.com/getsentry/sentry-javascript/pull/20288))
+- chore(deps-dev): Bump @sveltejs/kit from 2.53.3 to 2.57.1 ([#20216](https://github.com/getsentry/sentry-javascript/pull/20216))
+- chore(deps-dev): Bump vite from 7.2.0 to 7.3.2 in /dev-packages/e2e-tests/test-applications/tanstackstart-react ([#20107](https://github.com/getsentry/sentry-javascript/pull/20107))
+- chore(deps): Bump axios from 1.13.5 to 1.15.0 ([#20180](https://github.com/getsentry/sentry-javascript/pull/20180))
+- chore(deps): Bump axios from 1.13.5 to 1.15.0 in /dev-packages/e2e-tests/test-applications/nestjs-basic ([#20179](https://github.com/getsentry/sentry-javascript/pull/20179))
+- chore(deps): Bump hono from 4.12.7 to 4.12.12 ([#20118](https://github.com/getsentry/sentry-javascript/pull/20118))
+- chore(deps): Bump hono from 4.12.7 to 4.12.12 in /dev-packages/e2e-tests/test-applications/cloudflare-hono ([#20119](https://github.com/getsentry/sentry-javascript/pull/20119))
+- chore(deps): Bump next from 16.1.7 to 16.2.3 in nextjs-16-cf-workers ([#20289](https://github.com/getsentry/sentry-javascript/pull/20289))
+- chore(size-limit): Bump failing size limit scenario ([#20186](https://github.com/getsentry/sentry-javascript/pull/20186))
+- ci: Add automatic flaky test detector ([#18684](https://github.com/getsentry/sentry-javascript/pull/18684))
+- ci: Extract test names for flaky test issues ([#20298](https://github.com/getsentry/sentry-javascript/pull/20298))
+- ci: Remove Docker container for Verdaccio package publishing ([#20329](https://github.com/getsentry/sentry-javascript/pull/20329))
+- fix(ci): Prevent command injection in ci-metadata workflow ([#19899](https://github.com/getsentry/sentry-javascript/pull/19899))
+- fix(e2e-tests): Remove flaky navigation breadcrumb assertions from parameterized-routes tests ([#20202](https://github.com/getsentry/sentry-javascript/pull/20202))
+- fix(e2e): Add op check to waitForTransaction in React Router e2e tests ([#20193](https://github.com/getsentry/sentry-javascript/pull/20193))
+- fix(node-integration-tests): Fix flaky kafkajs test race condition ([#20189](https://github.com/getsentry/sentry-javascript/pull/20189))
+- ref(core): Add registry in Vercel ai integration ([#20098](https://github.com/getsentry/sentry-javascript/pull/20098))
+- ref(core): Automatically disable truncation when span streaming is enabled in Anthropic AI integration ([#20228](https://github.com/getsentry/sentry-javascript/pull/20228))
+- ref(core): Automatically disable truncation when span streaming is enabled in Google GenAI integration ([#20229](https://github.com/getsentry/sentry-javascript/pull/20229))
+- ref(core): Automatically disable truncation when span streaming is enabled in LangChain integration ([#20230](https://github.com/getsentry/sentry-javascript/pull/20230))
+- ref(core): Automatically disable truncation when span streaming is enabled in LangGraph integration ([#20231](https://github.com/getsentry/sentry-javascript/pull/20231))
+- ref(core): Automatically disable truncation when span streaming is enabled in OpenAI integration ([#20227](https://github.com/getsentry/sentry-javascript/pull/20227))
+- ref(core): Automatically disable truncation when span streaming is enabled in Vercel AI integration ([#20232](https://github.com/getsentry/sentry-javascript/pull/20232))
+- ref(core): Merge embeddings operations constants ([#20095](https://github.com/getsentry/sentry-javascript/pull/20095))
+- ref(core): Remove unused constants from vercel-ai-attributes.ts ([#20096](https://github.com/getsentry/sentry-javascript/pull/20096))
+- ref(nextjs): Refactor `findInjectionIndexAfterDirectives` for better readability ([#20310](https://github.com/getsentry/sentry-javascript/pull/20310))
+- ref(opentelemetry): Replace `@opentelemetry/resources` with inline `getSentryResource()` ([#20327](https://github.com/getsentry/sentry-javascript/pull/20327))
+- test: Fix flaky ANR test by increasing blocking duration ([#20239](https://github.com/getsentry/sentry-javascript/pull/20239))
+- test(bun): Add bun integration test folder ([#20286](https://github.com/getsentry/sentry-javascript/pull/20286))
+- test(cloudflare): Skip flaky durableobject-spans test ([#20282](https://github.com/getsentry/sentry-javascript/pull/20282))
+- test(openai): Use multi-message scenario in no-truncation test ([#20194](https://github.com/getsentry/sentry-javascript/pull/20194))
+- test(react): Remove duplicated test mock ([#20200](https://github.com/getsentry/sentry-javascript/pull/20200))
+- tests(ai): Fix streaming+truncation integration tests across AI integrations ([#20326](https://github.com/getsentry/sentry-javascript/pull/20326))
+
+
+
## 10.48.0
### Important Changes
diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json
index 2803a03c8eec..0c28fbfabce5 100644
--- a/dev-packages/browser-integration-tests/package.json
+++ b/dev-packages/browser-integration-tests/package.json
@@ -62,7 +62,7 @@
"@sentry-internal/rrweb": "2.34.0",
"@sentry/browser": "10.48.0",
"@supabase/supabase-js": "2.49.3",
- "axios": "1.13.5",
+ "axios": "1.15.0",
"babel-loader": "^10.1.1",
"fflate": "0.8.2",
"html-webpack-plugin": "^5.5.0",
diff --git a/dev-packages/browser-integration-tests/playwright.config.ts b/dev-packages/browser-integration-tests/playwright.config.ts
index 821c0291ccfb..681de57d4e59 100644
--- a/dev-packages/browser-integration-tests/playwright.config.ts
+++ b/dev-packages/browser-integration-tests/playwright.config.ts
@@ -30,7 +30,7 @@ const config: PlaywrightTestConfig = {
},
],
- reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list',
+ reporter: process.env.CI ? [['list'], ['github'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list',
globalSetup: require.resolve('./playwright.setup.ts'),
globalTeardown: require.resolve('./playwright.teardown.ts'),
diff --git a/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/init.js b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/init.js
new file mode 100644
index 000000000000..16e92edb9230
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/init.js
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/browser';
+import { viewHierarchyIntegration } from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [viewHierarchyIntegration()],
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/subject.js b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/subject.js
new file mode 100644
index 000000000000..f7060a33f05c
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/subject.js
@@ -0,0 +1 @@
+throw new Error('Some error');
diff --git a/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/template.html b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/template.html
new file mode 100644
index 000000000000..9e600d2a7e60
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/template.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+ Some title
+ Some text
+
+
diff --git a/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/test.ts b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/test.ts
new file mode 100644
index 000000000000..d3caf6ff9b3e
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/viewHierarchy/test.ts
@@ -0,0 +1,39 @@
+import { expect } from '@playwright/test';
+import type { ViewHierarchyData } from '@sentry/core';
+import { sentryTest } from '../../../utils/fixtures';
+import { getMultipleSentryEnvelopeRequests, envelopeParser } from '../../../utils/helpers';
+
+sentryTest('Captures view hierarchy as attachment', async ({ getLocalTestUrl, page }) => {
+ const bundle = process.env.PW_BUNDLE;
+ if (bundle != null && !bundle.includes('esm') && !bundle.includes('cjs')) {
+ sentryTest.skip();
+ }
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+
+ const [, events] = await Promise.all([
+ page.goto(url),
+ getMultipleSentryEnvelopeRequests(
+ page,
+ 1,
+ {},
+ req => envelopeParser(req)?.[4] as ViewHierarchyData,
+ ),
+ ]);
+
+ expect(events).toHaveLength(1);
+ const event: ViewHierarchyData = events[0];
+
+ expect(event.rendering_system).toBe('DOM');
+ expect(event.positioning).toBe('absolute');
+ expect(event.windows).toHaveLength(2);
+ expect(event.windows[0].type).toBe('h1');
+ expect(event.windows[0].visible).toBe(true);
+ expect(event.windows[0].alpha).toBe(1);
+ expect(event.windows[0].children).toHaveLength(0);
+
+ expect(event.windows[1].type).toBe('p');
+ expect(event.windows[1].visible).toBe(true);
+ expect(event.windows[1].alpha).toBe(1);
+ expect(event.windows[1].children).toHaveLength(0);
+});
diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts
index 08aad51de3ff..1373f78b3a5c 100644
--- a/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts
@@ -56,6 +56,66 @@ sentryTest('mutation after threshold results in slow click', async ({ forceFlush
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3501);
});
+sentryTest(
+ 'uses updated attributes for click breadcrumbs after mutation',
+ async ({ forceFlushReplay, getLocalTestUrl, page }) => {
+ if (shouldSkipReplayTest()) {
+ sentryTest.skip();
+ }
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+
+ const replayRequestPromise = waitForReplayRequest(page, 0);
+ const segmentReqWithClickBreadcrumbPromise = waitForReplayRequest(page, (_event, res) => {
+ const { breadcrumbs } = getCustomRecordingEvents(res);
+
+ return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
+ });
+
+ await page.goto(url);
+ await replayRequestPromise;
+
+ await forceFlushReplay();
+
+ await page.evaluate(() => {
+ const target = document.getElementById('next-question-button');
+ if (!target) {
+ throw new Error('Could not find target button');
+ }
+
+ target.id = 'save-note-button';
+ target.setAttribute('data-testid', 'save-note-button');
+ });
+
+ await page.getByRole('button', { name: 'Next question' }).click();
+ await forceFlushReplay();
+
+ const segmentReqWithClickBreadcrumb = await segmentReqWithClickBreadcrumbPromise;
+
+ const { breadcrumbs } = getCustomRecordingEvents(segmentReqWithClickBreadcrumb);
+ const updatedClickBreadcrumb = breadcrumbs.find(breadcrumb => breadcrumb.category === 'ui.click');
+
+ expect(updatedClickBreadcrumb).toEqual({
+ category: 'ui.click',
+ data: {
+ node: {
+ attributes: {
+ id: 'save-note-button',
+ testId: 'save-note-button',
+ },
+ id: expect.any(Number),
+ tagName: 'button',
+ textContent: '**** ********',
+ },
+ nodeId: expect.any(Number),
+ },
+ message: 'body > button#save-note-button',
+ timestamp: expect.any(Number),
+ type: 'default',
+ });
+ },
+);
+
sentryTest('multiple clicks are counted', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/template.html b/dev-packages/browser-integration-tests/suites/replay/slowClick/template.html
index 030401479a6b..2e0558870e1e 100644
--- a/dev-packages/browser-integration-tests/suites/replay/slowClick/template.html
+++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/template.html
@@ -6,6 +6,7 @@
Trigger mutation
+