diff --git a/sentry-php/1999/README.md b/sentry-php/1999/README.md new file mode 100644 index 0000000..d0a4671 --- /dev/null +++ b/sentry-php/1999/README.md @@ -0,0 +1,38 @@ +# Reproduction for sentry-php#1999 + +**Issue:** https://github.com/getsentry/sentry-php/issues/1999 + +## Description + +The SDK incorrectly uses the `sentry-sample_rate` value from the baggage header to make a sampling decision when the `sentry-trace` header is missing. Without a `sentry-trace` header, the SDK should treat this as a new trace head and use the locally configured `traces_sample_rate` instead. + +## Steps to Reproduce + +1. Install dependencies: + ```bash + composer install + ``` + +2. Run the reproduction: + ```bash + php reproduce.php + ``` + +## Expected Behavior + +When there is no `sentry-trace` header, the SDK should make an independent sampling decision using the configured `traces_sample_rate` (0.0 in this case). The transaction should NOT be sampled. + +## Actual Behavior + +The SDK extracts the sample rate from the incoming baggage header (`sentry-sample_rate=1.0`) and uses it to determine whether to sample the transaction. The transaction IS sampled despite `traces_sample_rate=0.0`. + +## Why This Matters + +- The baggage sample rate reflects the sampling decision of an upstream service that may have different sampling configuration +- Without `sentry-trace`, there's no parent span to continue, so the local SDK should be the trace head +- This can lead to unexpected sampling behavior where traces are sampled at rates not configured locally + +## Environment + +- PHP: 8.x +- sentry/sentry: ^4.0 diff --git a/sentry-php/1999/composer.json b/sentry-php/1999/composer.json new file mode 100644 index 0000000..d8e7e10 --- /dev/null +++ b/sentry-php/1999/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "sentry/sentry": "^4.0" + } +} diff --git a/sentry-php/1999/reproduce.php b/sentry-php/1999/reproduce.php new file mode 100644 index 0000000..ed6a9ec --- /dev/null +++ b/sentry-php/1999/reproduce.php @@ -0,0 +1,55 @@ + getenv('SENTRY_DSN') ?: null, + 'traces_sample_rate' => 0.0, // Local config: 0% sampling - should NEVER sample +]); + +// Simulate incoming request with baggage header but NO sentry-trace header +// This represents a scenario where: +// - An upstream service sent baggage with sample_rate=1.0 (100% sampling) +// - But the sentry-trace header is missing (e.g., stripped by a proxy) +$_SERVER['HTTP_BAGGAGE'] = 'sentry-sample_rate=1.0,sentry-sampled=true,sentry-trace_id=12345678901234567890123456789012,sentry-public_key=abc123'; + +// Note: HTTP_SENTRY_TRACE is intentionally NOT set + +$hub = SentrySdk::getCurrentHub(); +$client = $hub->getClient(); + +// Create a transaction context from the incoming headers +// Since there's no sentry-trace header, this should be treated as a new trace head +$context = TransactionContext::fromHeaders( + $_SERVER['HTTP_SENTRY_TRACE'] ?? '', + $_SERVER['HTTP_BAGGAGE'] ?? '' +); + +$context->setName('test-transaction'); +$context->setOp('http.server'); + +$transaction = $hub->startTransaction($context); + +echo "=== Reproduction for sentry-php#1999 ===\n\n"; +echo "Configuration:\n"; +echo " traces_sample_rate: 0.0 (0% - should never sample)\n\n"; + +echo "Incoming Headers:\n"; +echo " sentry-trace: (not present)\n"; +echo " baggage: sentry-sample_rate=1.0,sentry-sampled=true,...\n\n"; + +echo "Result:\n"; +echo " Transaction sampled: " . ($transaction->getSampled() ? 'YES' : 'NO') . "\n\n"; + +if ($transaction->getSampled()) { + echo "BUG CONFIRMED: Transaction was sampled despite traces_sample_rate=0.0\n"; + echo "The SDK incorrectly used the baggage sample_rate (1.0) instead of the local config.\n"; +} else { + echo "Expected behavior: Transaction was not sampled (respecting local config).\n"; +} + +$transaction->finish();