Skip to main content
During development, you can override default option values locally to test behavior changes without deploying to Kubernetes.

Quick start

The client library automatically searches for configuration files in your working directory:
1

Create values file

Create a values.json file in sentry-options/values/{namespace}/:
mkdir -p sentry-options/values/seer
cat > sentry-options/values/seer/values.json << 'EOF'
{
  "options": {
    "feature.enabled": true,
    "feature.rate_limit": 200
  }
}
EOF
2

Run your service

The client library will automatically pick up the local values:
python -m myapp
# or
cargo run
No environment variables or configuration needed - the library searches ./sentry-options/ automatically.
3

Test hot-reload

Modify values.json while your service is running:
# Update a value
cat > sentry-options/values/seer/values.json << 'EOF'
{
  "options": {
    "feature.enabled": true,
    "feature.rate_limit": 500
  }
}
EOF
Changes are picked up within 5 seconds (no restart required).

Directory structure

Your project should have this structure for local testing:
your-repo/
├── sentry-options/
│   ├── schemas/
│   │   └── {namespace}/
│   │       └── schema.json       # Your schema (already exists)
│   └── values/
│       └── {namespace}/
│           └── values.json        # Local test values (create this)
└── src/
    └── ...
The schemas/ directory already exists from your service setup. You only need to create the values/ directory for testing.

Configuration search order

The client library looks for configuration in this order:
  1. SENTRY_OPTIONS_DIR environment variable (if set)
  2. /etc/sentry-options (production path, if exists)
  3. ./sentry-options (local development fallback)
For local testing, use option 3 (working directory) or set SENTRY_OPTIONS_DIR:
# Use custom directory
export SENTRY_OPTIONS_DIR=/path/to/my/test/config
python -m myapp

Values file format

The values.json file must match the format generated by the CLI:
values.json
{
  "options": {
    "option-name": "value",
    "another-option": 42
  },
  "generated_at": "2026-01-21T17:00:00+00:00"
}
The generated_at field is optional for local testing but included for consistency.

Example from test suite

sentry-options/values/sentry-options-testing/values.json
{
  "options": {
    "example-option": "this is my string value"
  },
  "generated_at": "2026-01-21T17:00:00+00:00"
}

Testing scenarios

Testing feature flags

Test your code with features enabled/disabled:
{
  "options": {
    "feature.autofix.enabled": true,
    "feature.autofix.max_iterations": 5
  }
}

Testing rate limits

Test different rate limit thresholds:
{
  "options": {
    "api.rate_limit": 10,
    "api.burst_limit": 50
  }
}

Testing array values

Test with different organization lists:
{
  "options": {
    "feature.enabled_slugs": [
      "getsentry",
      "test-org",
      "demo-org"
    ]
  }
}

Testing defaults

Omit options to test schema defaults:
{
  "options": {
    "feature.enabled": true
    // feature.rate_limit will use schema default
  }
}

Hot-reload testing

The client library watches for file changes and reloads automatically:

Python test script

test_reload.py
from sentry_options import init, options
import time

init()
opts = options('seer')

while True:
    time.sleep(2)
    rate = opts.get('feature.rate_limit')
    enabled = opts.get('feature.enabled')
    print(f"Rate: {rate}, Enabled: {enabled}")
Run the script and modify values.json - changes appear within 5 seconds:
$ python test_reload.py
Rate: 100, Enabled: False
Rate: 100, Enabled: False
Rate: 200, Enabled: True   # <- values.json was updated
Rate: 200, Enabled: True

Rust test program

test_reload.rs
use sentry_options::{init, options};
use std::{thread::sleep, time::Duration};

fn main() -> anyhow::Result<()> {
    init()?;
    let opts = options("seer");

    loop {
        sleep(Duration::from_secs(2));
        let rate = opts.get("feature.rate_limit")?;
        let enabled = opts.get("feature.enabled")?;
        println!("Rate: {}, Enabled: {}", rate, enabled);
    }
}

Validation testing

Test schema validation by intentionally using invalid values:

Type mismatch

values.json (❌ Will fail validation)
{
  "options": {
    "feature.rate_limit": "not a number"
  }
}
Error:
SchemaError: Invalid type for feature.rate_limit: expected integer, got string

Unknown option

values.json (❌ Will fail validation)
{
  "options": {
    "feature.unknown_option": true
  }
}
Error:
SchemaError: Unknown option: feature.unknown_option

Invalid array elements

values.json (❌ Will fail validation)
{
  "options": {
    "feature.enabled_slugs": ["getsentry", 123]
  }
}
Error:
SchemaError: Invalid array element type: expected string, got integer

Multiple namespaces

You can test multiple namespaces simultaneously:
sentry-options/
├── schemas/
│   ├── seer/schema.json
│   └── seer-autofix/schema.json
└── values/
    ├── seer/values.json
    └── seer-autofix/values.json
from sentry_options import init, options

init()

seer_opts = options('seer')
autofix_opts = options('seer-autofix')

print(seer_opts.get('feature.enabled'))
print(autofix_opts.get('max_iterations'))

Testing in CI

You can use local values in automated tests:
tests/test_feature.py
import pytest
from sentry_options import init, options
import json
import os

@pytest.fixture(autouse=True)
def setup_test_options(tmp_path):
    """Create test options in temporary directory."""
    schemas_dir = tmp_path / "schemas" / "seer"
    values_dir = tmp_path / "values" / "seer"
    
    schemas_dir.mkdir(parents=True)
    values_dir.mkdir(parents=True)
    
    # Copy schema from repo
    schema = json.loads((Path("sentry-options/schemas/seer/schema.json")).read_text())
    (schemas_dir / "schema.json").write_text(json.dumps(schema))
    
    # Create test values
    values = {
        "options": {
            "feature.enabled": True,
            "feature.rate_limit": 200
        }
    }
    (values_dir / "values.json").write_text(json.dumps(values))
    
    # Point library to test directory
    os.environ["SENTRY_OPTIONS_DIR"] = str(tmp_path)
    init()

def test_feature_enabled():
    opts = options('seer')
    assert opts.get('feature.enabled') == True
    assert opts.get('feature.rate_limit') == 200

Common issues

Values not loading

Problem: Changes to values.json are not reflected Solution: Ensure the file is in the correct location:
# Should be:
sentry-options/values/{namespace}/values.json

# NOT:
sentry-options/{namespace}/values.json

Schema not found

Problem: UnknownNamespaceError when accessing options Solution: Verify schema exists and namespace name matches:
# Check schema exists
ls sentry-options/schemas/{namespace}/schema.json

# Namespace in code must match directory name
options('seer')  # Must match directory: sentry-options/schemas/seer/

Validation errors

Problem: Values fail validation on startup Solution: Validate your values.json format:
# Install CLI for validation
curl -sSL -o sentry-options-cli \
  https://github.com/getsentry/sentry-options/releases/download/0.0.14/sentry-options-cli-x86_64-unknown-linux-musl
chmod +x sentry-options-cli

# Validate
./sentry-options-cli validate-values \
  --schemas sentry-options/schemas \
  --values sentry-options/values/{namespace}/values.json

Next steps

Deploy values

Register your service in sentry-options-automator

Client usage

Learn more about the client library API

Build docs developers (and LLMs) love