# 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: 1. Create trackable tickets for errors 2. Assign errors to developers 3. Track resolution progress 4. 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: 1. **Run only on the test/staging server** (not production, not dev container) 2. **Poll all 6 Bugsink projects** for unresolved issues 3. **Create Gitea issues** with full error context 4. **Mark synced issues as resolved** in Bugsink (to prevent re-polling) 5. **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 ```bash # 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 ```markdown ## 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
Click to expand {stacktrace}
--- **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 1. **Redis check before creation**: Prevents duplicate Gitea issues 2. **Atomic Redis write after Gitea create**: Ensures state consistency 3. **Query only unresolved issues**: Resolved issues won't appear in polls 4. **No TTL on Redis keys**: Permanent sync history ## Consequences ### Positive 1. **Visibility**: All application errors become trackable tickets 2. **Accountability**: Errors can be assigned to developers 3. **History**: Complete audit trail of when errors were discovered and resolved 4. **Integration**: Errors appear alongside feature work in Gitea 5. **Automation**: No manual error triage required ### Negative 1. **API Dependencies**: Requires both Bugsink and Gitea APIs to be available 2. **Token Management**: Additional secrets to manage in CI/CD 3. **Potential Noise**: High-frequency errors could create many tickets (mitigated by Bugsink's issue grouping) 4. **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.ts` schema - Create `BugsinkClient` service (HTTP client) - Create `GiteaClient` service (HTTP client) - Add Redis db 15 connection for sync tracking ### Phase 2: Sync Logic - Create `BugsinkSyncService` with sync logic - Add `bugsink-sync` queue to `queues.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.yml` with 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 1. **Bi-directional sync**: Update Bugsink when Gitea issue is closed 2. **Smart deduplication**: Detect similar errors across projects 3. **Priority mapping**: High occurrence count → high priority label 4. **Slack/Discord notifications**: Alert on new critical errors 5. **Metrics dashboard**: Track error trends over time ## References - [ADR-006: Background Job Processing](./0006-background-job-processing-and-task-queues.md) - [ADR-015: Error Tracking and Observability](./0015-error-tracking-and-observability.md) - [Bugsink API Documentation](https://bugsink.com/docs/api/) - [Gitea API Documentation](https://docs.gitea.io/en-us/api-usage/)