Skip to content

Commit d7c330a

Browse files
committed
feat(health): add dedicated octokit GitHub App authentication check
1 parent f8d4813 commit d7c330a

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

backend/src/health/coordinator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { HealthResult, HealthCategory, HealthStepResult } from './types';
77
import { analyzeFailure } from '@/ai/utils/diagnostician';
88

99
// ─── Import ALL distributed modular checks ──────────────────────────────
10-
import { checkGitHubAPIHealth, checkWebhooksHealth } from '@/workflows/health';
10+
import { checkGitHubAPIHealth, checkWebhooksHealth, checkGitHubAppAuthHealth } from '@/workflows/health';
1111
import { checkHealth as checkAIHealth } from '@/ai/health';
1212
import { checkHealth as checkAgentsHealth } from '@/ai/agents/health';
1313
import { checkHealth as checkMCPHealth } from '@/ai/mcp/health';
@@ -29,6 +29,7 @@ const CODE_CHECKS: RegisteredCheck[] = [
2929
{ id: 'agents', category: 'agents', fn: checkAgentsHealth },
3030
{ id: 'mcp', category: 'mcp', fn: checkMCPHealth },
3131
{ id: 'browser', category: 'browser', fn: checkBrowserHealth },
32+
{ id: 'github_app',category: 'github', fn: checkGitHubAppAuthHealth },
3233
{ id: 'github', category: 'github', fn: checkGitHubAPIHealth },
3334
{ id: 'webhooks', category: 'webhooks', fn: checkWebhooksHealth },
3435
{ id: 'git', category: 'git', fn: checkGitHealth },
@@ -346,7 +347,7 @@ export class HealthCoordinator {
346347
const { limit = 20, onlyFailures = false, since } = options;
347348

348349
try {
349-
const conditions = [];
350+
const conditions: any[] = [];
350351
if (onlyFailures) {
351352
conditions.push(eq(healthRuns.status, 'unhealthy'));
352353
}

backend/src/workflows/health.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { getWebhooksDb } from '@/db';
22
import { webhookDeliveries } from '@/db/schemas/github/webhooks';
33
import { desc, gt, and, like } from 'drizzle-orm';
44
import { createGitHubIssue, createGitHubComment, updateGitHubIssue } from '@/ai/mcp/tools/github/github';
5-
import { HealthStepResult } from '@/health/health-check';
5+
import { HealthStepResult } from '@/health/types';
66
import { getGithubConfig } from '@utils/github/configs';
7+
import { getGitHubPrivateKey, getGitHubAppId } from '@/utils/secrets';
8+
import { App } from 'octokit';
79

810
export async function checkGitHubAPIHealth(env: Env): Promise<HealthStepResult> {
911
const start = Date.now();
@@ -66,6 +68,51 @@ export async function checkWebhooksHealth(env: Env): Promise<HealthStepResult> {
6668
}
6769
}
6870

71+
export async function checkGitHubAppAuthHealth(env: Env): Promise<HealthStepResult> {
72+
const start = Date.now();
73+
const details: any = { auth: { status: 'pending', test: 'octokit_app_init' } };
74+
75+
try {
76+
const appId = await getGitHubAppId(env);
77+
const privateKey = await getGitHubPrivateKey(env);
78+
79+
if (!appId || !privateKey) {
80+
throw new Error("Missing GitHub App ID or Private Key in bindings");
81+
}
82+
83+
// Initialize the Octokit App class.
84+
// If the private key is in PKCS#1 format (invalid), or malformed,
85+
// this instantiation or the subsequent JWT generation will throw.
86+
const app = new App({
87+
appId,
88+
privateKey,
89+
});
90+
91+
// Make an authenticated app-level request to force JWT generation
92+
// to strictly validate the key format internally
93+
const response = await app.octokit.request("GET /app");
94+
95+
details.auth.status = 'success';
96+
details.auth.appName = response.data?.name || "Unknown";
97+
98+
return {
99+
name: 'GitHub App Authentication',
100+
status: 'success',
101+
message: 'App initialized and JWT generated successfully (PKCS#8 confirmed)',
102+
details: details,
103+
durationMs: Date.now() - start
104+
};
105+
} catch (e: any) {
106+
return {
107+
name: 'GitHub App Authentication',
108+
status: 'failure',
109+
message: e.message || 'Failed to initialize Octokit App or generate JWT',
110+
details: { ...details, stack: e.stack, name: e.name },
111+
durationMs: Date.now() - start
112+
};
113+
}
114+
}
115+
69116
async function checkWebhookGaps(env: Env) {
70117
const db = getWebhooksDb(env.DB_WEBHOOKS);
71118
const lastEvents = await db.select().from(webhookDeliveries).orderBy(desc(webhookDeliveries.created_at)).limit(1);
@@ -81,7 +128,7 @@ async function checkWebhookGaps(env: Env) {
81128
async function runApiChecks(env: Env) {
82129
const owner = getGithubConfig(env, 'owner');
83130
const repo = env.HEALTH_TEST_REPO_NAME;
84-
const steps = [];
131+
const steps: any[] = [];
85132
let issueNumber: number | null = null;
86133

87134
try {

0 commit comments

Comments
 (0)