13 KiB
13 KiB
ADR-054: Bugsink to Gitea Issue Synchronization
Date: 2026-01-17
Status: Proposed
Context
The application uses Bugsink (Sentry-compatible self-hosted error tracking) to capture runtime errors across 6 projects:
| Project | Type | Environment |
|---|---|---|
| flyer-crawler-backend | Backend | Production |
| flyer-crawler-backend-test | Backend | Test/Staging |
| flyer-crawler-frontend | Frontend | Production |
| flyer-crawler-frontend-test | Frontend | Test/Staging |
| flyer-crawler-infrastructure | Infrastructure | Production |
| flyer-crawler-test-infrastructure | Infrastructure | Test/Staging |
Currently, errors remain in Bugsink until manually reviewed. There is no automated workflow to:
- Create trackable tickets for errors
- Assign errors to developers
- Track resolution progress
- Prevent errors from being forgotten
Decision
Implement an automated background worker that synchronizes unresolved Bugsink issues to Gitea as trackable tickets. The sync worker will:
- Run only on the test/staging server (not production, not dev container)
- Poll all 6 Bugsink projects for unresolved issues
- Create Gitea issues with full error context
- Mark synced issues as resolved in Bugsink (to prevent re-polling)
- Track sync state in Redis to ensure idempotency
Why Test/Staging Only?
- The sync worker is a background service that needs API tokens for both Bugsink and Gitea
- Running on test/staging provides a single sync point without duplicating infrastructure
- All 6 Bugsink projects (including production) are synced from this one worker
- Production server stays focused on serving users, not running sync jobs
Architecture
Component Overview
┌─────────────────────────────────────────────────────────────────────┐
│ TEST/STAGING SERVER │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ BullMQ Queue │───▶│ Sync Worker │───▶│ Redis DB 15 │ │
│ │ bugsink-sync │ │ (15min repeat) │ │ Sync State │ │
│ └──────────────────┘ └────────┬─────────┘ └───────────────┘ │
│ │ │
└───────────────────────────────────┼──────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Bugsink │ │ Gitea │
│ (6 projects) │ │ (1 repo) │
└──────────────┘ └──────────────┘
Queue Configuration
| Setting | Value | Rationale |
|---|---|---|
| Queue Name | bugsink-sync |
Follows existing naming pattern |
| Repeat Interval | 15 minutes | Balances responsiveness with API rate limits |
| Retry Attempts | 3 | Standard retry policy |
| Backoff | Exponential (30s base) | Handles temporary API failures |
| Concurrency | 1 | Serial processing prevents race conditions |
Redis Database Allocation
| Database | Usage | Owner |
|---|---|---|
| 0 | BullMQ (Production) | Existing queues |
| 1 | BullMQ (Test) | Existing queues |
| 2-14 | Reserved | Future use |
| 15 | Bugsink Sync State | This feature |
Redis Key Schema
bugsink:synced:{bugsink_issue_id}
└─ Value: JSON {
gitea_issue_number: number,
synced_at: ISO timestamp,
project: string,
title: string
}
Gitea Labels
The following labels have been created in torbo/flyer-crawler.projectium.com:
| Label | ID | Color | Purpose |
|---|---|---|---|
bug:frontend |
8 | #e11d48 (Red) | Frontend JavaScript/React errors |
bug:backend |
9 | #ea580c (Orange) | Backend Node.js/API errors |
bug:infrastructure |
10 | #7c3aed (Purple) | Infrastructure errors (Redis, PM2) |
env:production |
11 | #dc2626 (Dark Red) | Production environment |
env:test |
12 | #2563eb (Blue) | Test/staging environment |
env:development |
13 | #6b7280 (Gray) | Development environment |
source:bugsink |
14 | #10b981 (Green) | Auto-synced from Bugsink |
Label Mapping
| Bugsink Project | Bug Label | Env Label |
|---|---|---|
| flyer-crawler-backend | bug:backend | env:production |
| flyer-crawler-backend-test | bug:backend | env:test |
| flyer-crawler-frontend | bug:frontend | env:production |
| flyer-crawler-frontend-test | bug:frontend | env:test |
| flyer-crawler-infrastructure | bug:infrastructure | env:production |
| flyer-crawler-test-infrastructure | bug:infrastructure | env:test |
All synced issues also receive the source:bugsink label.
Implementation Details
New Files
| File | Purpose |
|---|---|
src/services/bugsinkSync.server.ts |
Core synchronization logic |
src/services/bugsinkClient.server.ts |
HTTP client for Bugsink API |
src/services/giteaClient.server.ts |
HTTP client for Gitea API |
src/types/bugsink.ts |
TypeScript interfaces for Bugsink responses |
src/routes/admin/bugsink-sync.ts |
Admin endpoints for manual trigger |
Modified Files
| File | Changes |
|---|---|
src/services/queues.server.ts |
Add bugsinkSyncQueue definition |
src/services/workers.server.ts |
Add sync worker implementation |
src/config/env.ts |
Add bugsink sync configuration schema |
.env.example |
Document new environment variables |
.gitea/workflows/deploy-to-test.yml |
Pass sync-related secrets |
Environment Variables
# Bugsink Configuration
BUGSINK_URL=https://bugsink.projectium.com
BUGSINK_API_TOKEN=77deaa5e... # Created via Django management command (see BUGSINK-SYNC.md)
# Gitea Configuration
GITEA_URL=https://gitea.projectium.com
GITEA_API_TOKEN=... # Personal access token with repo scope
GITEA_OWNER=torbo
GITEA_REPO=flyer-crawler.projectium.com
# Sync Control
BUGSINK_SYNC_ENABLED=false # Set true only in test environment
BUGSINK_SYNC_INTERVAL=15 # Minutes between sync runs
Gitea Issue Template
## Error Details
| Field | Value |
| ------------ | --------------- |
| **Type** | {error_type} |
| **Message** | {error_message} |
| **Platform** | {platform} |
| **Level** | {level} |
## Occurrence Statistics
- **First Seen**: {first_seen}
- **Last Seen**: {last_seen}
- **Total Occurrences**: {count}
## Request Context
- **URL**: {request_url}
- **Additional Context**: {context}
## Stacktrace
<details>
<summary>Click to expand</summary>
{stacktrace}
</details>
---
**Bugsink Issue**: {bugsink_url}
**Project**: {project_slug}
**Trace ID**: {trace_id}
Sync Workflow
1. Worker triggered (every 15 min or manual)
2. For each of 6 Bugsink projects:
a. List issues with status='unresolved'
b. For each issue:
i. Check Redis for existing sync record
ii. If already synced → skip
iii. Fetch issue details + stacktrace
iv. Create Gitea issue with labels
v. Store sync record in Redis
vi. Mark issue as 'resolved' in Bugsink
3. Log summary (synced: N, skipped: N, failed: N)
Idempotency Guarantees
- Redis check before creation: Prevents duplicate Gitea issues
- Atomic Redis write after Gitea create: Ensures state consistency
- Query only unresolved issues: Resolved issues won't appear in polls
- No TTL on Redis keys: Permanent sync history
Consequences
Positive
- Visibility: All application errors become trackable tickets
- Accountability: Errors can be assigned to developers
- History: Complete audit trail of when errors were discovered and resolved
- Integration: Errors appear alongside feature work in Gitea
- Automation: No manual error triage required
Negative
- API Dependencies: Requires both Bugsink and Gitea APIs to be available
- Token Management: Additional secrets to manage in CI/CD
- Potential Noise: High-frequency errors could create many tickets (mitigated by Bugsink's issue grouping)
- Single Point: Sync only runs on test server (if test server is down, no sync occurs)
Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Bugsink API rate limits | 15-minute polling interval |
| Gitea API rate limits | Sequential processing with delays |
| Redis connection issues | Reuse existing connection patterns |
| Duplicate issues | Redis tracking + idempotent checks |
| Missing stacktrace | Graceful degradation (create issue without trace) |
Admin Interface
Manual Sync Endpoint
POST /api/admin/bugsink/sync
Authorization: Bearer {admin_jwt}
Response:
{
"success": true,
"data": {
"synced": 3,
"skipped": 12,
"failed": 0,
"duration_ms": 2340
}
}
Sync Status Endpoint
GET /api/admin/bugsink/sync/status
Authorization: Bearer {admin_jwt}
Response:
{
"success": true,
"data": {
"enabled": true,
"last_run": "2026-01-17T10:30:00Z",
"next_run": "2026-01-17T10:45:00Z",
"total_synced": 47,
"projects": [
{ "slug": "flyer-crawler-backend", "synced_count": 12 },
...
]
}
}
Implementation Phases
Phase 1: Core Infrastructure
- Add environment variables to
env.tsschema - Create
BugsinkClientservice (HTTP client) - Create
GiteaClientservice (HTTP client) - Add Redis db 15 connection for sync tracking
Phase 2: Sync Logic
- Create
BugsinkSyncServicewith sync logic - Add
bugsink-syncqueue toqueues.server.ts - Add sync worker to
workers.server.ts - Create TypeScript types for API responses
Phase 3: Integration
- Add admin endpoints for manual sync trigger
- Update
deploy-to-test.ymlwith new secrets - Add secrets to Gitea repository settings
- Test end-to-end in staging environment
Phase 4: Documentation
- Update CLAUDE.md with sync information
- Create operational runbook for sync issues
Future Enhancements
- Bi-directional sync: Update Bugsink when Gitea issue is closed
- Smart deduplication: Detect similar errors across projects
- Priority mapping: High occurrence count → high priority label
- Slack/Discord notifications: Alert on new critical errors
- Metrics dashboard: Track error trends over time