Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate hybrid cloud tests for the Sentry codebase. Use when asked to "generate HC test", "create hybrid cloud test", "write HC test", "add HC test", "write RPC test", "test RPC service", "silo test", "cross-silo test", "outbox test", "API gateway test", or "endpoint silo test". Covers RPC service tests, API gateway tests, outbox pattern tests, and API endpoint tests with silo decorators.
.claude/skills/hybrid-cloud-test-gen/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✗→✓ | ▲ Improved | — | — |
This skill generates tests for Sentry's hybrid cloud architecture. It covers RPC services, API gateway proxying, outbox patterns, and endpoint silo decorators.
> ALWAYS use factory methods (self.create_user(), self.create_organization()) — never Model.objects.create().
> NEVER wrap factory method calls in assume_test_silo_mode or assume_test_silo_mode_of. Factories are silo-aware and handle silo mode internally. Only use silo mode context managers for direct ORM queries (Model.objects.get/filter/count/exists/delete).
> ALWAYS use pytest-style assertions (assert x == y) — never self.assertEqual().
> ALWAYS add tests to existing test files rather than creating new ones, unless no file exists for that module.
> For cross-silo ORM access: use assume_test_silo_mode_of(Model) when accessing a single model (auto-detects silo). Use assume_test_silo_mode(SiloMode.X) when the block covers multiple models or non-model operations.
> Use TestCase for most tests, including those using outbox_runner(). Only use TransactionTestCase when tests need real committed transactions (threading, concurrency, multi-process scenarios).
> NEVER use from __future__ import annotations in test files that deal with RPC models.
Determine which category of HC test to generate based on the user's request:
| Signal | Category | Go To | | ------------------------------------------------------------------- | -------------------- | ------ | | RPC service, service method, serialization round-trip, dispatch | RPC Service Tests | Step 3 | | API gateway, proxy, middleware, forwarding | API Gateway Tests | Step 4 | | Outbox, cross-silo message, ControlOutbox, CellOutbox, outbox drain | Outbox Pattern Tests | Step 5 | | API endpoint with silo decorator, endpoint test, permission check | Endpoint Silo Tests | Step 6 |
If the signal is ambiguous, ask the user to clarify which category.
Before generating any test:
@cell_silo_endpoint, @control_silo_endpoint, local_mode = SiloMode.X, or @cell_silo_model/@control_silo_model decorators.src/sentry/foo/bar.py → tests/sentry/foo/test_bar.pysrc/sentry/foo/services/bar/service.py → tests/sentry/foo/services/test_bar.pysrc/sentry/foo/services/bar/impl.py → tests/sentry/foo/services/test_bar.pyLoad references/rpc-service-tests.md for complete templates and patterns.
RPC service tests must cover:
@all_silo_test ensures the service works across all silo modesdispatch_to_local_service verifies args/return survive serializationoutbox_runner() + assume_test_silo_mode for propagation checks| Scenario | Decorator | Base Class | | ---------------------------------- | ----------------------------------------------- | -------------------------------- | | Standard RPC service | @all_silo_test | TestCase | | RPC with named cells | @all_silo_test(cells=create_test_cells("us")) | TestCase | | RPC with member mapping assertions | @all_silo_test | TestCase, HybridCloudTestMixin |
Load references/api-gateway-tests.md for complete templates and patterns.
API gateway tests verify that requests to control-silo endpoints are correctly proxied to the appropriate cell. They must cover:
close_streaming_response() for reading proxied response body| Scenario | Decorator | Base Class | | --------------------- | -------------------------------------------------------------------------------- | -------------------- | | Standard gateway test | @control_silo_test(cells=[ApiGatewayTestCase.CELL], include_monolith_run=True) | ApiGatewayTestCase |
Load references/outbox-tests.md for complete templates and patterns.
Outbox tests verify that cross-silo messages are created, drained, and produce the expected side effects. They must cover:
outbox_context(flush=False)outbox_runner() drains pending messagesassume_test_silo_mode_of(Model) to check replica/mapping state| Scenario | Decorator | Base Class | | --------------------------------- | -------------------- | --------------------- | | Control outbox test | @control_silo_test | TestCase | | Cell outbox test | @cell_silo_test | TestCase | | Outbox with threading/concurrency | (none) | TransactionTestCase |
Load references/endpoint-silo-tests.md for complete templates and patterns.
Endpoint silo tests verify that API endpoints work correctly under their declared silo mode. They must cover:
| Endpoint Decorator | Test Decorator | | ------------------------------------- | --------------------------------------------------- | | @cell_silo_endpoint | @cell_silo_test | | @control_silo_endpoint | @control_silo_test | | @control_silo_endpoint (with proxy) | @control_silo_test(cells=create_test_cells("us")) | | No decorator (monolith-only) | @no_silo_test |
Before presenting the generated test, verify against this checklist:
assume_test_silo_mode_of(Model) for single-model ORM access; assume_test_silo_mode(SiloMode.X) for multi-model/non-model ORM blocksself.create_*) are NEVER wrapped in assume_test_silo_modeModel.objects.create()pytest-style assertions only (assert x == y)TestCase for most tests; TransactionTestCase only for threading/concurrency)test_<action>_<scenario>)pytest -svv --reuse-db tests/sentry/path/to/test_file.pypython# Silo decorators from sentry.testutils.silo import ( all_silo_test, control_silo_test, cell_silo_test, no_silo_test, assume_test_silo_mode, assume_test_silo_mode_of, create_test_cells, ) # Base classes from sentry.testutils.cases import TestCase, TransactionTestCase, APITestCase # Cross-silo utilities from sentry.testutils.outbox import outbox_runner from sentry.testutils.hybrid_cloud import HybridCloudTestMixin from sentry.silo.base import SiloMode # RPC testing from sentry.hybridcloud.rpc.service import dispatch_to_local_service # API gateway testing from sentry.testutils.helpers.apigateway import ApiGatewayTestCase, verify_request_params # Outbox models from sentry.hybridcloud.models.outbox import ControlOutbox, CellOutbox, outbox_context from sentry.hybridcloud.outbox.category import OutboxCategory, OutboxScope
python# Use ONLY for direct ORM queries — never for factory calls assume_test_silo_mode(SiloMode.CONTROL) # Switch to control silo for ORM access assume_test_silo_mode(SiloMode.CELL) # Switch to cell silo for ORM access assume_test_silo_mode_of(ModelClass) # Switch to silo matching model's silo mode outbox_runner() # Drain all pending outboxes on exit outbox_context(flush=False) # Create outboxes without flushing override_cells(cells) # Override active cell config override_settings(SILO_MODE=SiloMode.X) # Override Django settings override_options({"key": value}) # Override Sentry options
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +59 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.