Monitoring Your Tegon AI Issue Tracker with Vigilmon
Tegon is an open-source, AI-first issue tracker and project management platform: think Linear with LLM features built in for automatic summarization, smart labeling, and priority suggestions — self-hosted and under your control.
Engineering teams that adopt Tegon integrate it into their daily flow quickly. When it goes down, so does their ability to triage bugs, plan sprints, and track GitHub/GitLab issue references. The AI features make it worse: background summarization jobs fail silently, and issues pile up without their auto-generated context.
This tutorial adds production monitoring to your Tegon deployment:
- Health check endpoints for the Next.js frontend and Node.js API
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring for AI summarization jobs and analytics workers
- WebSocket event stream and GitHub/GitLab webhook health checks
- Slack alerts and a public status page
Architecture overview
Tegon runs as two services with shared infrastructure:
| Component | Default port | What breaks when it's down | |-----------|-------------|---------------------------| | Next.js web app | 3000 | Team-facing UI, issue boards | | Node.js API backend | 3001 | All API calls, data mutations | | PostgreSQL (via Prisma) | 5432 | All data storage | | AI summarization worker | internal | Auto-summarization queue backs up | | GitHub/GitLab webhook receiver | 3001 | Issue link sync breaks | | WebSocket event stream | 3001 | Real-time updates stop | | File attachment storage | external/S3 | Attachments fail to upload | | Email notifications | external/SMTP | Issue assignment emails stop | | LLM integration endpoint | external | AI features return errors | | Analytics scheduler | internal | Usage metrics stop updating |
Step 1: Add health check endpoints
Next.js app health check (/api/health):
// src/app/api/health/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET() {
const checks: Record<string, string> = {};
// Database connectivity
try {
await prisma.$queryRaw`SELECT 1`;
checks.database = 'ok';
} catch {
checks.database = 'error';
}
// API backend reachability
try {
const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001';
const res = await fetch(`${apiUrl}/health`, { signal: AbortSignal.timeout(5000) });
checks.api = res.ok ? 'ok' : 'error';
} catch {
checks.api = 'error';
}
const healthy = Object.values(checks).every((v) => v === 'ok');
return NextResponse.json(
{ status: healthy ? 'ok' : 'degraded', checks },
{ status: healthy ? 200 : 503 }
);
}
Node.js API backend health check:
// src/routes/health.ts (Express/Fastify)
import { Router } from 'express';
import { prisma } from '../lib/prisma';
const router = Router();
router.get('/health', async (req, res) => {
const checks: Record<string, string> = {};
// Database
try {
await prisma.$queryRaw`SELECT 1`;
checks.database = 'ok';
} catch {
checks.database = 'error';
}
// LLM endpoint (if configured)
const llmEndpoint = process.env.LLM_ENDPOINT;
if (llmEndpoint) {
try {
const r = await fetch(`${llmEndpoint}/health`, {
signal: AbortSignal.timeout(5000),
});
checks.llm = r.ok ? 'ok' : 'degraded';
} catch {
checks.llm = 'unreachable';
}
}
const healthy = Object.values(checks).every((v) => v === 'ok');
res.status(healthy ? 200 : 503).json({
status: healthy ? 'ok' : 'degraded',
checks,
});
});
export default router;
Register the route in your API server before authentication middleware so Vigilmon doesn't need credentials.
Test both:
curl http://localhost:3000/api/health
# {"status":"ok","checks":{"database":"ok","api":"ok"}}
curl http://localhost:3001/health
# {"status":"ok","checks":{"database":"ok","llm":"ok"}}
Step 2: Set up HTTP monitoring in Vigilmon
Point Vigilmon at both services:
Web app monitor:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://tegon.yourdomain.com/api/health - Set check interval: 1 minute (paid) or 5 minutes (free)
- Save
API backend monitor:
- Click New Monitor → HTTP
- Enter
https://api.tegon.yourdomain.com/health(or your API subdomain) - Add keyword check for
"status":"ok"in response body - Save
Separate monitors for each service let you pinpoint failures. "API is down but web is up" is a completely different incident than "database is down and both services are failing."
Step 3: Monitor WebSocket event stream
Tegon uses WebSockets to push real-time updates to the issue board (new comments, status changes, assignments). Without it, users see stale data until they refresh.
Add a monitor for the WebSocket upgrade endpoint:
- Click New Monitor → HTTP
- Enter
https://api.tegon.yourdomain.com/socket(adjust path to your config) - Expected status: 400 or 426 (HTTP response to a non-WebSocket request — both are healthy indicators the server is running)
- Save
A 502 or 503 here means the Node.js process is down or the reverse proxy can't reach it.
Step 4: Alerts via Slack
In Vigilmon, go to Notifications → New Channel → Slack and paste your incoming webhook URL.
To create a Slack webhook:
- api.slack.com/apps → Create New App → From scratch
- Enable Incoming Webhooks → Add New Webhook
- Select your
#engineering-alertschannel → copy URL
Enable the Slack channel on both monitors. Downtime alerts reach engineers immediately:
🔴 DOWN: tegon.yourdomain.com/api/health
Status: 503 Service Unavailable
Checks: database=error, api=ok
Detected: EU-West, US-East
The checks detail in the response body tells you at a glance whether it's a database failure or an API process failure — you don't need to SSH in to figure out where to start.
Step 5: Heartbeat monitoring for AI workers
Tegon's AI features run as background jobs — issue summarization, priority suggestions, label recommendations. These fail silently when the LLM endpoint is unavailable or when the job queue backs up.
Summarization worker heartbeat:
// src/workers/ai-summarization.worker.ts
import { Worker } from 'bullmq';
import { redis } from '../lib/redis';
const worker = new Worker(
'ai-summarization',
async (job) => {
const { issueId } = job.data;
try {
await summarizeIssue(issueId);
await pingHeartbeat('summarization');
} catch (error) {
console.error(`Summarization failed for issue ${issueId}:`, error);
throw error; // BullMQ will retry
}
},
{ connection: redis }
);
async function pingHeartbeat(key: string) {
const url = process.env[`VIGILMON_HEARTBEAT_${key.toUpperCase()}`];
if (!url) return;
try {
await fetch(url, { signal: AbortSignal.timeout(5000) });
} catch (error) {
console.warn(`Heartbeat ping failed (${key}):`, error);
}
}
async function summarizeIssue(issueId: string) {
// existing summarization logic
}
Analytics scheduler heartbeat:
// src/workers/analytics-scheduler.ts
import cron from 'node-cron';
cron.schedule('0 * * * *', async () => {
try {
await runHourlyAnalytics();
await pingHeartbeat('analytics');
} catch (error) {
console.error('Analytics job failed:', error);
// No ping → Vigilmon alerts after window expires
}
});
In Vigilmon, create a Heartbeat Monitor for each worker:
- Click New Monitor → Heartbeat
- Set expected interval: 15 minutes for summarization worker, 2 hours for analytics scheduler
- Copy the ping URL → set as environment variable
- Enable Slack alerts
# .env
VIGILMON_HEARTBEAT_SUMMARIZATION=https://vigilmon.online/heartbeat/your-token
VIGILMON_HEARTBEAT_ANALYTICS=https://vigilmon.online/heartbeat/your-other-token
If the LLM endpoint goes down and the summarization worker's retry budget exhausts, the missing heartbeat alerts you — before your team notices that new issues stopped getting AI-generated summaries.
Step 6: Monitor GitHub/GitLab webhook delivery
Tegon syncs issue references from GitHub/GitLab PRs via webhooks. If your webhook receiver stops accepting events, issue links go out of sync silently.
Add a monitor for your webhook receiver endpoint:
- Click New Monitor → HTTP
- Enter
https://api.tegon.yourdomain.com/api/v1/webhooks/github(adjust to your path) - Expected status: 405 Method Not Allowed (a GET request to a POST-only endpoint — this is healthy)
- Save
A 405 confirms the route is registered and the server is handling requests. A 502 or 404 means the API is down or the route was removed.
Also add monitors for the external platforms:
https://api.github.com → keyword: "current_user_url"
https://gitlab.com/api/v4/version → keyword: "version"
These catch GitHub/GitLab incidents that would cause webhook delivery failures on their end, not yours.
Step 7: Monitor file storage and email delivery
File attachment storage (S3/MinIO):
// src/lib/storage-health.ts
import { S3Client, HeadBucketCommand } from '@aws-sdk/client-s3';
export async function checkStorage(): Promise<'ok' | 'error'> {
const client = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION ?? 'us-east-1',
});
try {
await client.send(new HeadBucketCommand({
Bucket: process.env.S3_BUCKET ?? 'tegon-attachments',
}));
return 'ok';
} catch {
return 'error';
}
}
Include this in your API health check:
checks.storage = await checkStorage();
When storage goes down, file uploads fail with cryptic errors. Having it in the health check makes the cause immediately visible.
Step 8: Status page and badge
Status page:
- Go to Status Pages → New Status Page in Vigilmon
- Add all monitors: web app, API, WebSocket, AI worker heartbeats, webhook receiver
- Group them: "Core Services", "AI Features", "Integrations"
- Copy the public URL
Share the status page in your team's #general channel. Engineers can check service health during incidents without needing SSH access.
README badge:
[](https://status.tegon.yourdomain.com)
What you've built
| What | How |
|------|-----|
| Next.js web health | /api/health with DB + API backend checks |
| Node.js API health | /health with DB + LLM endpoint checks |
| WebSocket stream | HTTP monitor (expects 400/426) |
| Slack alerts | Vigilmon Slack notification channel |
| AI summarization worker | BullMQ worker heartbeat ping |
| Analytics scheduler | Cron heartbeat ping every hour |
| GitHub/GitLab webhooks | HTTP monitor on webhook endpoint (expects 405) |
| File storage health | S3/MinIO check in API health response |
| Status page | Vigilmon public status page |
Your LLMs summarize issues. Vigilmon makes sure the service doing the summarizing stays up.
Next steps
- Add response time monitoring to track when LLM calls start slowing down before they fail
- Monitor your Prisma migration status with a heartbeat on your migration runner
- Set up separate heartbeat monitors per LLM feature (priority suggestions, label recommendations)
- If you use MinIO for storage, add a direct monitor for the MinIO health endpoint at
:9001/minio/health/live
Get started free at vigilmon.online.