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
19 changes: 12 additions & 7 deletions packages/status/probes.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const packageAppOrigin = 'https://kodyapps.dev'

function healthyRoutes(): Record<string, FakeRoute> {
return {
[`${primaryOrigin}/health`]: { body: { ok: true, commitSha: 'abc' } },
[`${primaryOrigin}/health`]: {
body: { ok: true, commitSha: 'abc123def4567890abcdef1234567890abcdef12' },
},
[`${primaryOrigin}/mcp`]: {
status: 401,
headers: { 'WWW-Authenticate': 'Bearer resource_metadata="..."' },
Expand Down Expand Up @@ -56,21 +58,24 @@ async function probe(routes: Record<string, FakeRoute>) {
}

function outcome(
outcomes: Awaited<ReturnType<typeof runAllProbes>>,
result: Awaited<ReturnType<typeof runAllProbes>>,
component: string,
) {
return outcomes.find((entry) => entry.component === component)
return result.outcomes.find((entry) => entry.component === component)
}

test('a fully healthy pass reports every component ok', async () => {
const outcomes = await probe(healthyRoutes())
expect(outcomes.map((entry) => entry.component).toSorted()).toEqual(
const result = await probe(healthyRoutes())
expect(result.outcomes.map((entry) => entry.component).toSorted()).toEqual(
[...statusComponentIds].toSorted(),
)
for (const entry of outcomes) {
for (const entry of result.outcomes) {
expect(entry.ok, `${entry.component} should be ok`).toBe(true)
}
expect(outcome(outcomes, 'app_db')?.latencyMs).toBe(4)
expect(outcome(result, 'app_db')?.latencyMs).toBe(4)
expect(result.productionCommitSha).toBe(
'abc123def4567890abcdef1234567890abcdef12',
)
})

test('probe failures isolate to the affected component and map error details', async () => {
Expand Down
50 changes: 37 additions & 13 deletions packages/status/probes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,32 +65,51 @@ async function timedFetch(
}
}

type AppHealthBody = {
ok?: boolean
commitSha?: string
}

function readProductionCommitSha(body: AppHealthBody | null): string | null {
const commitSha = body?.commitSha?.trim()
if (!commitSha || !/^[0-9a-f]{7,40}$/i.test(commitSha)) return null
return commitSha.toLowerCase()
Comment on lines +73 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle non-string commitSha values.

At Line 74, .trim() runs without runtime type validation. A malformed health response with a numeric or object commitSha rejects runAllProbes. This stops status sampling, incident processing, and pruning for that run. Treat non-string values as invalid and return null. Add an integration test for a non-string commitSha.

Proposed fix
 function readProductionCommitSha(body: AppHealthBody | null): string | null {
-	const commitSha = body?.commitSha?.trim()
+	const rawCommitSha = body?.commitSha
+	if (typeof rawCommitSha !== 'string') return null
+	const commitSha = rawCommitSha.trim()
 	if (!commitSha || !/^[0-9a-f]{7,40}$/i.test(commitSha)) return null
 	return commitSha.toLowerCase()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function readProductionCommitSha(body: AppHealthBody | null): string | null {
const commitSha = body?.commitSha?.trim()
if (!commitSha || !/^[0-9a-f]{7,40}$/i.test(commitSha)) return null
return commitSha.toLowerCase()
function readProductionCommitSha(body: AppHealthBody | null): string | null {
const rawCommitSha = body?.commitSha
if (typeof rawCommitSha !== 'string') return null
const commitSha = rawCommitSha.trim()
if (!commitSha || !/^[0-9a-f]{7,40}$/i.test(commitSha)) return null
return commitSha.toLowerCase()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/status/probes.ts` around lines 73 - 76, Update
readProductionCommitSha to verify body?.commitSha is a string before calling
trim, returning null for numeric, object, or other non-string values while
preserving the existing validation and lowercase normalization for valid
strings. Add an integration test covering a non-string commitSha and confirm
runAllProbes continues without rejecting.

}

async function probeApp(
fetcher: typeof fetch,
primaryOrigin: string,
): Promise<ProbeOutcome> {
): Promise<{ outcome: ProbeOutcome; productionCommitSha: string | null }> {
const result = await timedFetch(fetcher, `${primaryOrigin}/health`)
if (!result.response) {
return {
component: 'app',
ok: false,
latencyMs: result.latencyMs,
detail: result.error,
outcome: {
component: 'app',
ok: false,
latencyMs: result.latencyMs,
detail: result.error,
},
productionCommitSha: null,
}
}
let body: AppHealthBody | null = null
let bodyOk = false
try {
const body = (await result.response.json()) as { ok?: boolean }
body = (await result.response.json()) as AppHealthBody
bodyOk = body.ok === true
} catch {
body = null
bodyOk = false
}
const ok = result.response.ok && bodyOk
return {
component: 'app',
ok,
latencyMs: result.latencyMs,
detail: ok ? null : `HTTP ${result.response.status}`,
outcome: {
component: 'app',
ok,
latencyMs: result.latencyMs,
detail: ok ? null : `HTTP ${result.response.status}`,
},
productionCommitSha: readProductionCommitSha(body),
}
}

Expand Down Expand Up @@ -184,17 +203,22 @@ async function probeStorageComponents(
})
}

export type ProbeRunResult = {
outcomes: Array<ProbeOutcome>
productionCommitSha: string | null
}

export async function runAllProbes(
config: ProbeConfig,
): Promise<Array<ProbeOutcome>> {
): Promise<ProbeRunResult> {
const fetcher = config.fetcher ?? fetch
const [app, mcp, packageApps, storage] = await Promise.all([
probeApp(fetcher, config.primaryOrigin),
probeMcp(fetcher, config.primaryOrigin),
probePackageApps(fetcher, config.packageAppOrigin),
probeStorageComponents(fetcher, config.primaryOrigin),
])
const outcomes = [app, mcp, packageApps, ...storage]
const outcomes = [app.outcome, mcp, packageApps, ...storage]
const covered = new Set(outcomes.map((outcome) => outcome.component))
for (const component of statusComponentIds) {
if (!covered.has(component)) {
Expand All @@ -206,5 +230,5 @@ export async function runAllProbes(
})
}
}
return outcomes
return { outcomes, productionCommitSha: app.productionCommitSha }
}
10 changes: 9 additions & 1 deletion packages/status/status-page.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function snapshot(overrides: Partial<StatusSnapshot> = {}): StatusSnapshot {
openIncidents: [],
recentIncidents: [],
providerIncidents: null,
buildCommit: 'abc123',
productionCommit: 'abc123def4567890abcdef1234567890abcdef12',
...overrides,
}
}
Expand All @@ -44,6 +44,11 @@ test('status page renders components, incidents, unknown state, and escapes deta
expect(healthy).toContain(component.name.replaceAll('&', '&amp;'))
}
expect(healthy).toContain('99.98% uptime')
expect(healthy).toContain('Production commit')
expect(healthy).toContain(
'https://github.com/kentcdodds/kody/commit/abc123def4567890abcdef1234567890abcdef12',
)
expect(healthy).toContain('>abc123d<')
expect(healthy).toContain('http-equiv="refresh"')
expect(healthy).toMatch(/operational|All systems/i)

Expand Down Expand Up @@ -133,6 +138,9 @@ test('status page renders provider incidents separately and omits them when abse
expect(withProvider).toContain('for context only')
expect(withProvider).toContain('All systems operational')

const withoutCommit = renderStatusPage(snapshot({ productionCommit: null }))
expect(withoutCommit).not.toContain('Production commit')

const unsafeLink = renderStatusPage(
snapshot({
providerIncidents: [
Expand Down
16 changes: 15 additions & 1 deletion packages/status/status-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ function renderProviderIncident(incident: ProviderIncident): string {
</div>`
}

/** Public GitHub repository for the main kody worker (production deploys). */
const productionRepo = 'kentcdodds/kody'

function productionCommitLink(commitSha: string): string {
const shortSha = commitSha.slice(0, 7)
const href = `https://github.com/${productionRepo}/commit/${escapeHtml(commitSha)}`
return `<a href="${href}">${escapeHtml(shortSha)}</a>`
}

function renderProductionCommit(commitSha: string | null): string {
if (!commitSha) return ''
return `Production commit ${productionCommitLink(commitSha)} · `
}

function renderProviderIncidentsSection(
incidents: Array<ProviderIncident> | null | undefined,
): string {
Expand Down Expand Up @@ -267,7 +281,7 @@ export function renderStatusPage(snapshot: StatusSnapshot): string {
${providerIncidents}
${recentIncidents}
<footer>
Probes run every minute from an independently deployed worker.
${renderProductionCommit(snapshot.productionCommit)}Probes run every minute from an independently deployed worker.
<a href="https://heykody.app">heykody.app</a> ·
<a href="/status.json">JSON</a>
</footer>
Expand Down
8 changes: 6 additions & 2 deletions packages/status/status-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from './status-types.ts'

const providerIncidentsMetaKey = 'provider_incidents_cache'
const productionCommitMetaKey = 'production_commit_sha'

export type StatusWorkerEnv = {
STATUS_STORE: DurableObjectNamespace<StatusStore>
Expand Down Expand Up @@ -385,11 +386,14 @@ export class StatusStore extends DurableObject<StatusWorkerEnv> {
}

async runProbes(): Promise<void> {
const outcomes = await runAllProbes({
const { outcomes, productionCommitSha } = await runAllProbes({
primaryOrigin: this.env.PRIMARY_ORIGIN,
packageAppOrigin: this.env.PACKAGE_APP_ORIGIN,
})
const now = Date.now()
if (productionCommitSha) {
this.setMeta(productionCommitMetaKey, productionCommitSha)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale production commit persists

Medium Severity

runProbes only writes production_commit_sha when productionCommitSha is truthy, so a successful production GET /health with a missing or invalid commitSha leaves the previous SHA in meta. The footer and /status.json can then show an outdated production commit while probes are otherwise healthy.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0fb3e35. Configure here.

for (const outcome of outcomes) {
this.recordOutcome(outcome, now)
}
Expand Down Expand Up @@ -417,7 +421,7 @@ export class StatusStore extends DurableObject<StatusWorkerEnv> {
openIncidents: this.listIncidents('open'),
recentIncidents: this.listIncidents('resolved'),
providerIncidents: this.readProviderIncidents(now),
buildCommit: this.env.BUILD_COMMIT ?? null,
productionCommit: this.getMeta(productionCommitMetaKey),
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/status/status-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ export type StatusSnapshot = {
* omits the provider section in that case (fail-soft).
*/
providerIncidents: Array<ProviderIncident> | null
buildCommit: string | null
/** Latest `commitSha` reported by production `GET /health` (main worker). */
productionCommit: string | null
}
Loading