Skip to content

feat(eval): GlitchTip event ingester for regression cases#1820

Merged
chernistry merged 1 commit into
mainfrom
feat/glitchtip-to-eval-ingester
May 21, 2026
Merged

feat(eval): GlitchTip event ingester for regression cases#1820
chernistry merged 1 commit into
mainfrom
feat/glitchtip-to-eval-ingester

Conversation

@chernistry

Copy link
Copy Markdown
Collaborator

Summary

Adds the inbound half of the GlitchTip integration: open unresolved
issues become P1 (warn-only) regression eval cases in the existing
incident-synthesis pipeline. The outbound DSN path already shipped; this
closes the loop so production exceptions feed the quality gate.

  • scripts/scrape_glitchtip_events.py lists is:unresolved issues
    across all projects in the org, follows Link: rel="next" pagination,
    fetches each issue's latest event for the exception type/value and the
    deepest in-app stack frame, reads the environment / release /
    server_name tags, and emits one JSON record per unique issue.
  • IncidentSynthesizer gains a GlitchTipIncident dataclass routed
    through the existing _synthesize_eval_case seam (the same seam used
    by DLQ entries, post-mortems, and CI-failure post-mortems from feat(eval): ingest CI-failure post-mortems into regression cases #1808).
    Each case carries source_incident: glitchtip-issue:<issue_id> and
    severity: P1.
  • Reuses the glitchtip_insights base-URL / DSN resolution and HTTP
    auth shape. No host is hardcoded anywhere; the
    test_no_hardcoded_infra guard passes for the package, the new doc,
    and the new workflow.

Current GlitchTip issues (org bernstein)

id shortId title disposition
1 BERNSTEIN-1 glitchtip smoke from operator finalisation wiring probe -> resolved in smoke
2 BERNSTEIN-2 glitchtip insights wiring probe wiring probe -> resolved in smoke
3 BERNSTEIN-3 RuntimeError: max_retries_exhausted (ci-verify) becomes a P1 regression case
4 BERNSTEIN-4 RuntimeError: max_retries_exhausted (ci-verify) becomes a P1 regression case

Dedup contract

Re-runs are a pure no-op. Three axes merge before any write:

  1. Scraper JSON output keyed on glitchtip_issue_id.
  2. Emitted YAML cases whose source_incident is glitchtip-issue:<id>.
  3. Synthesizer content hash (inc-<sha1[:12]>) plus the
    source_incident slug at write time.

Wiring-probe filter

DEFAULT_WIRING_PROBE_ALLOW_LIST filters the two administrative smoke
issues (glitchtip insights wiring probe,
glitchtip smoke from operator finalisation). Matching is
case-insensitive and substring-based; add entries in the script or via
repeatable --wiring-probe flags. Documented in
docs/operations/glitchtip-ingester.md.

Env-var matrix

Setting Primary Alias Required Default
API token BERNSTEIN_GLITCHTIP_TOKEN GLITCHTIP_API_TOKEN yes (else exit 0) none
Base URL BERNSTEIN_GLITCHTIP_BASE_URL GLITCHTIP_BASE_URL yes, or a DSN host none
Org slug BERNSTEIN_GLITCHTIP_ORG GLITCHTIP_ORG_SLUG no bernstein

Base URL falls back to the host of BERNSTEIN_GLITCHTIP_DSN /
BERNSTEIN_TELEMETRY_DSN / GLITCHTIP_DSN. No host literal ships.

Smoke run trace

Authenticated to the live server as the operator and resolved the two
administrative wiring-probe issues via the API:

resolving issue 1 -> status now: resolved | glitchtip smoke from operator finalisation
resolving issue 2 -> status now: resolved | glitchtip insights wiring probe
unresolved count after: 2  (BERNSTEIN-3, BERNSTEIN-4 RuntimeError)

Scraper graceful-exit path (no token configured):

WARNING: BERNSTEIN_GLITCHTIP_TOKEN / GLITCHTIP_API_TOKEN not set; scraper exits 0 with no output
scraper finished; 0 new record(s)   (exit 0, no output dir created)

Workflow

.github/workflows/glitchtip-ingester.yml runs daily at 23 6 * * *
(6 min after the Sonar sweeper), with workflow_dispatch severity /
max-per-day inputs mirroring the Sonar sweeper, and opens a PR with the
emitted cases. Cron is gated by ENABLE_CRON (default 0) until a
clean dispatch smoke run.

Test plan

  • uv run pytest tests/unit/eval/ -q --no-cov --timeout=120 (175 passed)
  • uv run pytest tests/unit/observability/test_no_hardcoded_infra.py (2 passed)
  • uv run ruff check . && uv run ruff format (clean)
  • pyright on the new modules introduces zero new errors over the baseline
  • live smoke: 2 wiring-probe issues resolved on the server

Add scripts/scrape_glitchtip_events.py which lists open is:unresolved
issues across the org, fetches each issue's latest event for the
exception type and deepest in-app stack frame, and emits one JSON
record per unique issue. The IncidentSynthesizer gains a
GlitchTipIncident dataclass routed through the same _synthesize_eval_case
seam, producing one P1 (warn-only) eval-case YAML per record keyed on
glitchtip-issue:<issue_id>.

Reuses the existing glitchtip_insights base-URL/DSN resolution and HTTP
auth shape; no host is hardcoded, so the package and public artefacts
stay backend-agnostic. Dedup spans the scraper output, emitted YAMLs,
and the synthesizer content hash. A configurable allow-list filters the
administrative wiring-probe issues. A daily workflow opens a PR with the
emitted cases for operator review, cron disabled until smoke passes.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @chernistry, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@chernistry has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 79262970-9044-4a85-b7fd-14caf9301ca6

📥 Commits

Reviewing files that changed from the base of the PR and between 2242d16 and 2b55261.

⛔ Files ignored due to path filters (7)
  • tests/fixtures/glitchtip/event_with_secret.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/event_with_stacktrace.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/issues_empty.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/issues_page1.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/issues_page2.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/issues_single.json is excluded by !tests/fixtures/**
  • tests/fixtures/glitchtip/issues_with_probes.json is excluded by !tests/fixtures/**
📒 Files selected for processing (5)
  • .github/workflows/glitchtip-ingester.yml
  • docs/operations/glitchtip-ingester.md
  • scripts/scrape_glitchtip_events.py
  • src/bernstein/eval/incident_synthesizer.py
  • tests/unit/eval/test_glitchtip_ingester.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/glitchtip-to-eval-ingester

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Sonar insights (advisory, no merge-block)

Snapshot of bernstein on the configured Sonar instance:

Metric Value
Coverage 13.5
Code smells 151
Bugs 11
Vulnerabilities 2
Security hotspots 91

Run bernstein doctor sonar locally for the full surface.

This comment is a soft signal. The Sonar scan runs on push to main; the PR check itself never fails on smells.

@github-actions

Copy link
Copy Markdown
Contributor

Review-bot acknowledgement summary

  • Must-address findings: 0 (0 acknowledged, 0 open)
  • Informational findings: 0

All must-address findings are resolved or acknowledged.

@github-actions

Copy link
Copy Markdown
Contributor

bernstein doctor observe for PR #1820 (feat/glitchtip-to-eval-ingester): ok=0, warn=2, fail=0, error=0, skipped=2

sonar -- WARN (project bernstein)

metric value delta threshold status
coverage_pct 13.5% new 80.0% fail
code_smells 151 new 50 warn
bugs 11 new 0 fail
vulnerabilities 2 new 0 warn
security_hotspots 91 new 0 fail

code-scanning -- WARN (33 open alert(s))

metric value delta threshold status
open_alerts 33 new 0 fail
critical_alerts 0 new 0 ok
high_alerts 2 new 0 warn
medium_alerts 0 new - ok
low_alerts 0 new - ok
Skipped backends (credentials not configured)
  • glitchtip: BERNSTEIN_GLITCHTIP_TOKEN not set
  • dt: DTRACK_URL/TOKEN/PROJECT not set

See docs/observability/unified-doctor.md for backend setup notes.

@chernistry chernistry enabled auto-merge (squash) May 21, 2026 21:22
@chernistry chernistry merged commit c0dc996 into main May 21, 2026
64 of 65 checks passed
@chernistry chernistry deleted the feat/glitchtip-to-eval-ingester branch May 21, 2026 21:22
@github-actions

Copy link
Copy Markdown
Contributor

Contract drift detected - proposed patch

Inline autofix push failed (failure). Apply the patch below manually.

Three contract tests act as drift detectors against the public CLI / API surface:

  • tests/unit/test_readme_api_coverage.py::test_all_cli_commands_are_documented
  • tests/unit/test_api_v1_routing.py::TestVersionedRoutesParity::test_every_root_route_has_v1_counterpart
  • tests/unit/test_cli_run_params.py::test_run_params_match_cli_call

One or more failed on this PR. scripts/regen_contract_drift.py produced the patch below (2 LOC, cap: 30).

Files changed:

tests/unit/test_readme_api_coverage.py

How to apply

Either run the regen script locally:

uv run python scripts/regen_contract_drift.py --fixture all
git add -A && git commit -m "chore(ci): regenerate contract drift allow-lists"
git push

Or apply the patch directly:

gh pr checkout 1820
git apply <<'PATCH'
diff --git a/tests/unit/test_readme_api_coverage.py b/tests/unit/test_readme_api_coverage.py
index fa6b08a6..90b3d71d 100644
--- a/tests/unit/test_readme_api_coverage.py
+++ b/tests/unit/test_readme_api_coverage.py
@@ -239,6 +239,8 @@ DOCUMENTED_COMMANDS: frozenset[str] = frozenset(
         "desktop-register",
         # Bot-added: drift autofix (regen_contract_drift.py)
         "supervisor",
+        # Bot-added: drift autofix (regen_contract_drift.py)
+        "schedule",
     }
 )
PATCH
git add -A && git commit -m "chore(ci): regenerate contract drift allow-lists"
git push
Full diff
diff --git a/tests/unit/test_readme_api_coverage.py b/tests/unit/test_readme_api_coverage.py
index fa6b08a6..90b3d71d 100644
--- a/tests/unit/test_readme_api_coverage.py
+++ b/tests/unit/test_readme_api_coverage.py
@@ -239,6 +239,8 @@ DOCUMENTED_COMMANDS: frozenset[str] = frozenset(
         "desktop-register",
         # Bot-added: drift autofix (regen_contract_drift.py)
         "supervisor",
+        # Bot-added: drift autofix (regen_contract_drift.py)
+        "schedule",
     }
 )

Source CI run: https://github.com/sipyourdrink-ltd/bernstein/actions/runs/26253850668

Refs #1273.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant