diff --git a/.github/workflows/status-smoke.yml b/.github/workflows/status-smoke.yml
new file mode 100644
index 00000000..d87380a0
--- /dev/null
+++ b/.github/workflows/status-smoke.yml
@@ -0,0 +1,19 @@
+name: Status Smoke Test
+on:
+ schedule:
+ - cron: "0 9 * * 1"
+ workflow_dispatch:
+permissions:
+ contents: read
+jobs:
+ status-smoke:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: 24.18.0
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm test:status-smoke
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1eb2d411..320c2130 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -33,11 +33,12 @@ Fine-grained PATs need Actions (read), Contents (read), Issues (read), and Pull
## Running checks
```bash
-pnpm test # unit tests (Vitest — root + mcp/)
-pnpm test:e2e # Playwright E2E tests (chromium)
-pnpm run typecheck # TypeScript validation (root + mcp/)
-pnpm run screenshot # Capture dashboard screenshot (saves to docs/)
-pnpm mcp:serve # Start the MCP server (requires GITHUB_TOKEN)
+pnpm test # unit tests (Vitest — root + mcp/)
+pnpm test:e2e # Playwright E2E tests (chromium)
+pnpm test:status-smoke # Live-network check that githubstatus.com still reports the tracked component names (also runs on a weekly schedule in CI)
+pnpm run typecheck # TypeScript validation (root + mcp/)
+pnpm run screenshot # Capture dashboard screenshot (saves to docs/)
+pnpm mcp:serve # Start the MCP server (requires GITHUB_TOKEN)
```
To test MCP tools interactively, use the MCP Inspector:
@@ -98,7 +99,7 @@ Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
type(scope): description
```
-Scope is optional. Use imperative mood: "add feature", not "adds feature" or "added feature".
+Scope is optional. Use present-indicative mood: "adds feature", not "add feature" or "added feature".
## Releasing the MCP server
diff --git a/README.md b/README.md
index 5388c389..f10df639 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,10 @@ A second, faster poll loop (default 30s, configurable 10–120s) targets only in
Browser notifications for new issues, PRs, and failed runs. Per-type toggles in settings. Notification permission requested on first enable. New items are detected via the Events API polling loop and full refresh cycles.
+### GitHub Status Badge
+
+A status dot in the header reflects GitHub's own reported status for the services this dashboard depends on (Actions, API Requests, Git Operations, Issues, Pull Requests). Click it for incident details and a link to githubstatus.com. Toast and drawer notifications fire on incident start and resolution.
+
### Repo Pinning and Reordering
Lock repos to the top of each tab's list so they don't shift around as activity changes. Drag-to-reorder within the locked set. Lock controls appear on hover on desktop, always visible on mobile.
diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md
index 697ccd97..21d14ee5 100644
--- a/docs/USER_GUIDE.md
+++ b/docs/USER_GUIDE.md
@@ -15,6 +15,7 @@ GitHub Tracker is a dashboard that aggregates open issues, pull requests, and Gi
- [Personal Summary Strip](#personal-summary-strip)
- [Repo Grouping and Expand/Collapse](#repo-grouping-and-expandcollapse)
- [Scope Filter](#scope-filter)
+ - [GitHub Status Badge](#github-status-badge)
- [Issues Tab](#issues-tab)
- [Filters](#issues-filters)
- [Dependency Dashboard Toggle](#dependency-dashboard-toggle)
@@ -148,6 +149,12 @@ The **Scope** filter chip appears on the Issues and Pull Requests tabs when you
The scope filter is hidden (and always set to "Involves me") when you have no tracked users and no monitor-all repos, because in that configuration all fetched data already involves you.
+### GitHub Status Badge
+
+A small status dot appears in the header, next to your avatar. It reflects GitHub's own reported status for the services this dashboard depends on — Actions, API Requests, Git Operations, Issues, and Pull Requests. Green means all tracked services are operational; yellow, orange, or red indicate minor, major, or critical disruption respectively.
+
+Click the badge to see details on any active incident, including affected components and the latest status update, plus a link to [githubstatus.com](https://www.githubstatus.com). Other GitHub services (Copilot, Codespaces, Pages, Packages, etc.) are not tracked by this badge.
+
---
## Issues Tab
@@ -369,6 +376,10 @@ Setting the interval to **Off** disables automatic polling; manual refresh still
A ±30 second jitter is applied to the refresh interval to avoid synchronized API spikes from multiple browser tabs.
+### GitHub Status Checks
+
+Each full refresh also checks GitHub's own status page for outages affecting Actions, API Requests, Git Operations, Issues, or Pull Requests. This is a single lightweight, unauthenticated request and does not count against your GitHub API rate limit. It follows the same schedule as the full refresh — including the same visibility-based pausing — rather than running on its own timer.
+
### Hot Poll
A second, faster poll loop runs alongside the full refresh specifically for in-flight items. It targets:
@@ -410,6 +421,10 @@ When you return to a tab that has been hidden for more than 2 minutes, a catch-u
The bell icon in the header opens the notification drawer, which shows API errors, rate limit warnings, and other system messages. Notifications are dismissed automatically when the underlying condition clears on the next poll cycle.
+### GitHub Outage Notifications
+
+When a GitHub outage affecting a tracked service starts, a toast appears and an entry is added to the notification drawer. A second notification confirms it once the outage resolves. These always appear via the in-app toast and drawer — they are not part of the **Browser Push Notifications** toggles below and cannot be disabled.
+
### Browser Push Notifications
Browser push notifications are disabled by default. To enable them:
diff --git a/package.json b/package.json
index a9b8308e..c0e99dcd 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"typecheck": "tsc --build src/shared/tsconfig.json && tsc --noEmit && tsc --build mcp/tsconfig.json",
"test:e2e": "E2E_PORT=$(node -e \"const s=require('net').createServer();s.listen(0,()=>{console.log(s.address().port);s.close()})\") playwright test",
"test:waf": "bash scripts/waf-smoke-test.sh",
+ "test:status-smoke": "vitest run --config vitest.smoke.config.ts",
"screenshot": "pnpm exec playwright test --config playwright.config.screenshot.ts",
"mcp:serve": "pnpm --filter github-tracker-mcp dev",
"validate:deploy": "bash scripts/validate-deploy.sh"
diff --git a/public/_headers b/public/_headers
index e0ecdf6e..3dff81e6 100644
--- a/public/_headers
+++ b/public/_headers
@@ -1,5 +1,5 @@
/*
- Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-uEFqyYCMaNy1Su5VmWLZ1hOCRBjkhm4+ieHHxQW6d3Y=' https://challenges.cloudflare.com; style-src-elem 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: https://avatars.githubusercontent.com; connect-src 'self' https://api.github.com https://api.atlassian.com ws://127.0.0.1:*; font-src 'self'; worker-src 'self'; manifest-src 'self'; frame-src https://challenges.cloudflare.com; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests; report-uri /api/csp-report; report-to csp-endpoint
+ Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-uEFqyYCMaNy1Su5VmWLZ1hOCRBjkhm4+ieHHxQW6d3Y=' https://challenges.cloudflare.com; style-src-elem 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: https://avatars.githubusercontent.com; connect-src 'self' https://api.github.com https://api.atlassian.com https://www.githubstatus.com ws://127.0.0.1:*; font-src 'self'; worker-src 'self'; manifest-src 'self'; frame-src https://challenges.cloudflare.com; frame-ancestors 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests; report-uri /api/csp-report; report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="/api/csp-report"
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
diff --git a/src/app/components/layout/Header.tsx b/src/app/components/layout/Header.tsx
index 87ce5888..3b6865ca 100644
--- a/src/app/components/layout/Header.tsx
+++ b/src/app/components/layout/Header.tsx
@@ -5,6 +5,7 @@ import { getUnreadCount, markAllAsRead } from "../../lib/errors";
import NotificationDrawer from "../shared/NotificationDrawer";
import ToastContainer from "../shared/ToastContainer";
import { Tooltip } from "../shared/Tooltip";
+import GitHubStatusBadge from "../shared/GitHubStatusBadge";
export default function Header() {
const navigate = useNavigate();
@@ -56,6 +57,8 @@ export default function Header() {
)}
+
+
= {
+ none: { bg: "bg-success", label: "All systems operational", pulse: false },
+ minor: { bg: "bg-warning", label: "Minor GitHub service disruption", pulse: false },
+ major: { bg: "bg-orange-500", label: "Major GitHub service outage", pulse: true },
+ critical: { bg: "bg-red-500", label: "Critical GitHub service outage", pulse: true },
+};
+
+export default function GitHubStatusBadge() {
+ const status = createMemo(() => getGitHubStatus());
+ const cfg = createMemo(() => {
+ const s = status();
+ return s !== null
+ ? SEVERITY_CONFIG[s.severity]
+ : { bg: "bg-base-content/20", label: "Checking GitHub status…", pulse: false };
+ });
+ const statusSummaryRow = () => (
+