Skill: Uptime Monitoring

## Overview

7 stars

Best use case

Skill: Uptime Monitoring is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## Overview

Teams using Skill: Uptime Monitoring should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/uptime-monitoring/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/web-applications/status-page/skills/uptime-monitoring/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/uptime-monitoring/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Skill: Uptime Monitoring Compares

Feature / AgentSkill: Uptime MonitoringStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## Overview

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Skill: Uptime Monitoring

## Overview

Pattern for scheduled HTTP health checks using `node-cron` and `axios`, including concurrent execution control, status normalization, DB write patterns, and cron job lifecycle management tied to service CRUD operations.

## Health Check Implementation

```ts
// api/src/lib/checker.ts
import axios, { type AxiosError } from 'axios';
import type { Database } from 'better-sqlite3';
import type { Service } from '../types';

export interface CheckResult {
  status: 'up' | 'down' | 'timeout' | 'error';
  statusCode: number | null;
  responseMs: number | null;
  errorMessage: string | null;
}

export async function checkService(service: Service): Promise<CheckResult> {
  const start = Date.now();
  try {
    const res = await axios.get(service.url, {
      timeout: service.timeout_seconds * 1000,
      validateStatus: () => true,   // never throw on non-2xx
      maxRedirects: 3,
    });
    const responseMs = Date.now() - start;
    const status = res.status === service.expected_status ? 'up' : 'down';
    return { status, statusCode: res.status, responseMs, errorMessage: null };
  } catch (err: unknown) {
    const responseMs = Date.now() - start;
    const e = err as AxiosError;
    if (e.code === 'ECONNABORTED' || e.message?.includes('timeout')) {
      return { status: 'timeout', statusCode: null, responseMs: null, errorMessage: 'Request timed out' };
    }
    return { status: 'error', statusCode: null, responseMs: null, errorMessage: e.message ?? 'Unknown error' };
  }
}

export function writeCheck(db: Database, serviceId: number, result: CheckResult): void {
  db.prepare(`
    INSERT INTO checks (service_id, status, status_code, response_ms, error_message)
    VALUES (?, ?, ?, ?, ?)
  `).run(serviceId, result.status, result.statusCode, result.responseMs, result.errorMessage);
}
```

## Scheduler with Concurrency Control

```ts
// api/src/lib/scheduler.ts
import cron, { type ScheduledTask } from 'node-cron';
import type { Database } from 'better-sqlite3';
import { checkService, writeCheck } from './checker';
import { maybeOpenAutoIncident } from './incidents';
import type { Service } from '../types';

// Map from service ID to cron task
const tasks = new Map<number, ScheduledTask>();

// Semaphore: limit concurrent checks
let running = 0;
const MAX_CONCURRENT = Number(process.env.CHECK_CONCURRENCY ?? 5);

async function runCheck(db: Database, service: Service): Promise<void> {
  if (running >= MAX_CONCURRENT) return;  // skip if at capacity
  running++;
  try {
    const result = await checkService(service);
    writeCheck(db, service.id, result);
    if (result.status !== 'up' && service.auto_incident) {
      maybeOpenAutoIncident(db, service.id);
    }
  } finally {
    running--;
  }
}

function cronExpression(intervalMinutes: number): string {
  if (intervalMinutes === 1) return '* * * * *';
  return `*/${intervalMinutes} * * * *`;
}

export function scheduleService(db: Database, service: Service): void {
  // Stop existing task if any
  unscheduleService(service.id);

  if (!service.is_active) return;

  const task = cron.schedule(cronExpression(service.check_interval), () => {
    runCheck(db, service);
  });

  tasks.set(service.id, task);
}

export function unscheduleService(serviceId: number): void {
  const task = tasks.get(serviceId);
  if (task) {
    task.stop();
    tasks.delete(serviceId);
  }
}

export function initScheduler(db: Database): void {
  const services = db.prepare('SELECT * FROM services WHERE is_active = 1').all() as Service[];
  for (const s of services) {
    scheduleService(db, s);
  }
}
```

Call `initScheduler(db)` once at server startup after DB migrations.

## Scheduler Integration with Service Routes

```ts
// api/src/routes/services.ts (excerpt)
import { scheduleService, unscheduleService } from '../lib/scheduler';

// After creating a service:
router.post('/', requireAuth, (req, res) => {
  // ... insert service ...
  const service = db.prepare('SELECT * FROM services WHERE id = ?').get(newId) as Service;
  scheduleService(db, service);
  res.status(201).json(service);
});

// After updating a service:
router.put('/:id', requireAuth, (req, res) => {
  // ... update service ...
  const service = db.prepare('SELECT * FROM services WHERE id = ?').get(req.params.id) as Service;
  scheduleService(db, service);  // reschedule with new interval
  res.json(service);
});

// After deleting a service:
router.delete('/:id', requireAuth, (req, res) => {
  unscheduleService(Number(req.params.id));
  db.prepare('DELETE FROM services WHERE id = ?').run(req.params.id);
  res.json({ ok: true });
});
```

## Manual Check Endpoint

```ts
// POST /api/services/:id/check
router.post('/:id/check', requireAuth, async (req, res) => {
  const service = db.prepare('SELECT * FROM services WHERE id = ?').get(req.params.id) as Service | undefined;
  if (!service) { res.status(404).json({ error: 'Not found' }); return; }

  const result = await checkService(service);
  writeCheck(db, service.id, result);

  res.json({ status: result.status, responseMs: result.responseMs, statusCode: result.statusCode });
});
```

## Cron Expression Reference

| Interval | Expression | Notes |
|---|---|---|
| 1 minute | `* * * * *` | Every minute |
| 2 minutes | `*/2 * * * *` | Every 2 minutes |
| 5 minutes | `*/5 * * * *` | Every 5 minutes |
| 10 minutes | `*/10 * * * *` | Every 10 minutes |
| 30 minutes | `*/30 * * * *` | Every 30 minutes |
| 1 hour | `0 * * * *` | Top of every hour |

## Check Status Normalization

| Scenario | Status |
|---|---|
| Response received, correct status code | `up` |
| Response received, wrong status code | `down` |
| `axios` timeout (ECONNABORTED) | `timeout` |
| Network error (ECONNREFUSED, ENOTFOUND) | `error` |
| DNS resolution failure | `error` |

## Uptime Color Scale

| Uptime | Color | CSS var |
|---|---|---|
| >= 99% | Green | `--status-up` (#16a34a) |
| 95-99% | Amber | `--status-degraded` (#d97706) |
| < 95% | Red | `--status-down` (#dc2626) |

## Testing the Checker

```ts
// api/src/lib/__tests__/checker.test.ts
import { describe, test, expect, vi } from 'vitest';
import axios from 'axios';

vi.mock('axios');

test('returns up when status code matches expected', async () => {
  vi.mocked(axios.get).mockResolvedValueOnce({ status: 200 });
  const result = await checkService({ url: 'https://example.com', expected_status: 200, timeout_seconds: 10, ...rest });
  expect(result.status).toBe('up');
  expect(result.statusCode).toBe(200);
});

test('returns down when status code does not match', async () => {
  vi.mocked(axios.get).mockResolvedValueOnce({ status: 503 });
  const result = await checkService({ url: 'https://example.com', expected_status: 200, timeout_seconds: 10, ...rest });
  expect(result.status).toBe('down');
});

test('returns timeout on ECONNABORTED', async () => {
  const err = new Error('timeout') as Error & { code: string };
  err.code = 'ECONNABORTED';
  vi.mocked(axios.get).mockRejectedValueOnce(err);
  const result = await checkService({ url: 'https://example.com', expected_status: 200, timeout_seconds: 10, ...rest });
  expect(result.status).toBe('timeout');
});
```

Related Skills

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Pastebin Core

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Cost Reporting

7
from heldernoid/agentic-build-templates

## Overview