Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ point is `evaluateWizardReadiness()`, which returns one of three values:
| --- | --- |
| `types.ts` | Enums, interfaces (`ServiceHealthStatus`, `AllServicesHealth`, etc.) |
| `statuspage.ts` | Statuspage.io v2 API helpers + checks for Anthropic, PostHog, GitHub, npm, Cloudflare |
| `endpoints.ts` | Direct endpoint checks for LLM Gateway (`/_liveness`), MCP (`/`), and the skills origins (`skill-menu.json` on GitHub Releases + the AWS mirror) |
| `endpoints.ts` | Direct endpoint checks for MCP (`/`) and the skills origins (`skill-menu.json` on GitHub Releases + the AWS mirror) |
| `readiness.ts` | `checkAllExternalServices`, `evaluateWizardReadiness`, readiness config |
| `index.ts` | Barrel re-export |
| `testme.md` | Test running instructions and endpoint reference |
Expand All @@ -632,10 +632,14 @@ two arrays:
### Current defaults

```ts
downBlocksRun: ['anthropic', 'npmOverall', 'llmGateway', 'mcp', 'skillsOrigin'],
downBlocksRun: ['anthropic', 'npmOverall', 'mcp', 'skillsOrigin'],
degradedBlocksRun: ['anthropic'],
```

The AI gateway is deliberately absent: its URL is only known from the run's
token mint at bootstrap, so there is nothing static to probe — and a failed
mint already stops the run with the server's reason.

`skillsOrigin` is one entry covering two origins: skills are published to
GitHub Releases and an AWS mirror under the same filenames, and downloads fail
over between them (`src/lib/fetch-retry.ts`). Both are probed in parallel, so
Expand Down
130 changes: 49 additions & 81 deletions src/lib/health-checks/__tests__/health-checks.test.ts

Large diffs are not rendered by default.

10 changes: 2 additions & 8 deletions src/lib/health-checks/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import { ServiceHealthStatus, type BaseHealthResult } from './types';
// NoConnection means we don't know whose fault it is; readiness reconciles
// against the status page before deciding how to surface it to the user.
//
// LLM Gateway – FastAPI service
// Source: posthog/services/llm-gateway/src/llm_gateway/api/health.py
// GET /_liveness → 200 {"status":"alive"}
//
// MCP – Cloudflare Worker
// Source: posthog/services/mcp/src/index.ts
// GET / → 302 to posthog.com docs. The redirect proves the worker is up.
Expand Down Expand Up @@ -65,7 +61,8 @@ async function attemptFetch(
}
}

async function fetchEndpointHealth(
// Exported so tests can pin the retry/taxonomy machinery directly.
export async function fetchEndpointHealth(
url: string,
timeoutMs = 5000,
isExpectedStatus: (status: number) => boolean = (s) => s === 200,
Expand Down Expand Up @@ -137,9 +134,6 @@ async function fetchEndpointHealth(
return result;
}

export const checkLlmGatewayHealth = (): Promise<BaseHealthResult> =>
fetchEndpointHealth('https://gateway.us.posthog.com/_liveness');

export const checkMcpHealth = (): Promise<BaseHealthResult> =>
fetchEndpointHealth(
'https://mcp.posthog.com/',
Expand Down
6 changes: 1 addition & 5 deletions src/lib/health-checks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ export {
resetPosthogHealthCache,
} from './incidentio';

export {
checkLlmGatewayHealth,
checkMcpHealth,
checkSkillsOriginHealth,
} from './endpoints';
export { checkMcpHealth, checkSkillsOriginHealth } from './endpoints';

export {
type WizardReadinessConfig,
Expand Down
36 changes: 12 additions & 24 deletions src/lib/health-checks/readiness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import {
checkPosthogOverallHealth,
checkPosthogComponentHealth,
} from './incidentio';
import {
checkLlmGatewayHealth,
checkMcpHealth,
checkSkillsOriginHealth,
} from './endpoints';
import { checkMcpHealth, checkSkillsOriginHealth } from './endpoints';
import { logToFile } from '@utils/debug';

// ---------------------------------------------------------------------------
Expand All @@ -37,7 +33,6 @@ export const SERVICE_LABELS: Record<HealthCheckKey, string> = {
npmComponents: 'npm (components)',
cloudflareOverall: 'Cloudflare',
cloudflareComponents: 'Cloudflare (components)',
llmGateway: 'LLM Gateway',
mcp: 'MCP',
skillsOrigin: 'Skills download',
};
Expand All @@ -56,26 +51,24 @@ export interface WizardReadinessConfig {
/**
* See README section "Health checks" for the full rationale.
* Adjust these arrays to change what blocks a wizard run.
*
* The AI gateway is not probed: its URL is only known from the run's token
* mint, and a failed mint already stops the run at bootstrap with the
* server's reason.
*/
export const DEFAULT_WIZARD_READINESS_CONFIG: WizardReadinessConfig = {
downBlocksRun: [
'anthropic',
'npmOverall',
'llmGateway',
'mcp',
'skillsOrigin',
],
downBlocksRun: ['anthropic', 'npmOverall', 'mcp', 'skillsOrigin'],
degradedBlocksRun: ['anthropic'],
};

/**
* Reduced readiness config for --signup provisioning flows.
*
* Provisioning only needs PostHog and the LLM Gateway - it doesn't
* use Anthropic directly, npm, the skills origins, or MCP.
* Provisioning only needs PostHog - it doesn't use Anthropic directly, npm,
* the skills origins, or MCP.
*/
export const SIGNUP_WIZARD_READINESS_CONFIG: WizardReadinessConfig = {
downBlocksRun: ['posthogOverall', 'llmGateway'],
downBlocksRun: ['posthogOverall'],
};

// ---------------------------------------------------------------------------
Expand All @@ -92,7 +85,6 @@ export async function checkAllExternalServices(): Promise<AllServicesHealth> {
npmComponents,
cloudflareOverall,
cloudflareComponents,
llmGateway,
mcp,
skillsOrigin,
] = await Promise.all([
Expand All @@ -104,7 +96,6 @@ export async function checkAllExternalServices(): Promise<AllServicesHealth> {
checkNpmComponentHealth(),
checkCloudflareOverallHealth(),
checkCloudflareComponentHealth(),
checkLlmGatewayHealth(),
checkMcpHealth(),
checkSkillsOriginHealth(),
]);
Expand All @@ -118,7 +109,6 @@ export async function checkAllExternalServices(): Promise<AllServicesHealth> {
npmComponents,
cloudflareOverall,
cloudflareComponents,
llmGateway,
mcp,
skillsOrigin,
};
Expand All @@ -131,7 +121,7 @@ export async function checkAllExternalServices(): Promise<AllServicesHealth> {
* official status page (`posthogstatus.com`):
*
* - Status page says PostHog is `Down` / `Degraded` → upgrade
* llmGateway / mcp to `Down`. The status page corroborates.
* mcp to `Down`. The status page corroborates.
* - Status page is `Healthy` → keep `NoConnection`. The status page
* contradicts; this is probably the user's network.
* - Status page is also `NoConnection` → keep `NoConnection`. User
Expand All @@ -145,11 +135,11 @@ export async function checkAllExternalServices(): Promise<AllServicesHealth> {
* when incident.io's API parsed successfully and reported a real
* `partial_outage` or `degraded_performance` for some component. That's
* PostHog acknowledging an issue, even if narrower than a full outage.
* If our gateway probe is also failing, those two signals together
* If our MCP probe is also failing, those two signals together
* justify pointing at PostHog rather than the user.
*
* A narrower variant — only corroborate when the affected component is
* gateway-related (LLM, US/EU Cloud, app) — would be more precise. We
* MCP-related (US/EU Cloud, app) — would be more precise. We
* have the data in `posthogComponents` but don't use it here. If the
* analytics show false positives concentrated in this case, it's a
* cheap follow-up.
Expand Down Expand Up @@ -179,7 +169,6 @@ export function reconcilePosthogReachability(

return {
...health,
llmGateway: upgrade(health.llmGateway),
mcp: upgrade(health.mcp),
};
}
Expand Down Expand Up @@ -346,7 +335,6 @@ function allUnknown(error: string): AllServicesHealth {
npmComponents: { ...base },
cloudflareOverall: base,
cloudflareComponents: { ...base },
llmGateway: base,
mcp: base,
skillsOrigin: base,
};
Expand Down
8 changes: 0 additions & 8 deletions src/lib/health-checks/testme.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ responses captured from production endpoints on 2026-03-05.
| npm (components) | `https://status.npmjs.org/api/v2/summary.json` | Adds `components[]` array |
| Cloudflare | `https://www.cloudflarestatus.com/api/v2/status.json` | Same shape |
| Cloudflare (components) | `https://www.cloudflarestatus.com/api/v2/summary.json` | Adds `components[]` array |
| LLM Gateway | `https://gateway.us.posthog.com/_liveness` | `{"status":"alive"}` (HTTP 200) |
| MCP | `https://mcp.posthog.com/` | HTML landing page (HTTP 200) |

### Statuspage.io API v2 reference
Expand All @@ -54,13 +53,6 @@ responses captured from production endpoints on 2026-03-05.
- Component docs:
<https://support.atlassian.com/statuspage/docs/show-service-status-with-components>

### LLM Gateway

- Source: `posthog/services/llm-gateway/src/llm_gateway/api/health.py`
- `GET /` → `{"service":"llm-gateway","status":"running"}`
- `GET /_liveness` → `{"status":"alive"}` (no DB dependency)
- `GET /_readiness` → `{"status":"ready"}` (checks Postgres with `SELECT 1`)

### MCP

- Source: `posthog/services/mcp/src/index.ts`
Expand Down
1 change: 0 additions & 1 deletion src/lib/health-checks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export interface AllServicesHealth {
npmComponents: ComponentHealthResult;
cloudflareOverall: BaseHealthResult;
cloudflareComponents: ComponentHealthResult;
llmGateway: BaseHealthResult;
mcp: BaseHealthResult;
skillsOrigin: BaseHealthResult;
}
Expand Down
5 changes: 0 additions & 5 deletions src/ui/tui/playground/demos/HealthCheckDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const MOCK_CONFIRMED_OUTAGE: AllServicesHealth = {
},
cloudflareOverall: HEALTHY,
cloudflareComponents: { status: ServiceHealthStatus.Healthy },
llmGateway: HEALTHY,
mcp: HEALTHY,
skillsOrigin: HEALTHY,
};
Expand All @@ -58,10 +57,6 @@ const MOCK_NO_CONNECTION: AllServicesHealth = {
npmComponents: { status: ServiceHealthStatus.Healthy },
cloudflareOverall: HEALTHY,
cloudflareComponents: { status: ServiceHealthStatus.Healthy },
llmGateway: {
status: ServiceHealthStatus.NoConnection,
error: 'getaddrinfo ENOTFOUND gateway.us.posthog.com',
},
mcp: {
status: ServiceHealthStatus.NoConnection,
error: 'fetch failed',
Expand Down
2 changes: 1 addition & 1 deletion src/ui/tui/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function captureHealthCheckBlocked(result: WizardReadinessResult): void {
const posthogStatus = health.posthogOverall?.status;
const retriesUsed = Math.max(
0,
...(['llmGateway', 'mcp', 'skillsOrigin'] as const).map((k) => {
...(['mcp', 'skillsOrigin'] as const).map((k) => {
const ind = health[k]?.rawIndicator ?? '';
const m = ind.match(/attempts=(\d+)/);
return m ? Number(m[1]) - 1 : 0;
Expand Down
Loading