Skip to content

Commit 8415459

Browse files
authored
feat: anthropic status check (#236)
1 parent 3f89ea4 commit 8415459

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

src/lib/agent-runner.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
addMCPServerToClientsStep,
3131
uploadEnvironmentVariablesStep,
3232
} from '../steps';
33+
import { checkAnthropicStatusWithPrompt } from '../utils/anthropic-status';
3334

3435
/**
3536
* Universal agent-powered wizard runner.
@@ -54,6 +55,15 @@ export async function runAgentWizard(
5455
);
5556
}
5657

58+
// Check Anthropic/Claude service status before proceeding
59+
const statusOk = await checkAnthropicStatusWithPrompt({ ci: options.ci });
60+
if (!statusOk) {
61+
await abort(
62+
`Please try again later, or set up ${config.metadata.name} manually: ${config.metadata.docsUrl}`,
63+
0,
64+
);
65+
}
66+
5767
const cloudRegion = options.cloudRegion ?? (await askForCloudRegion());
5868
const typeScriptDetected = isUsingTypeScript(options);
5969

src/utils/anthropic-status.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import clack from './clack';
2+
import chalk from 'chalk';
3+
4+
const CLAUDE_STATUS_URL = 'https://status.claude.com/api/v2/status.json';
5+
const CLAUDE_STATUS_PAGE = 'https://status.claude.com';
6+
7+
type StatusIndicator = 'none' | 'minor' | 'major' | 'critical';
8+
9+
interface ClaudeStatusResponse {
10+
page: {
11+
id: string;
12+
name: string;
13+
url: string;
14+
time_zone: string;
15+
updated_at: string;
16+
};
17+
status: {
18+
indicator: StatusIndicator;
19+
description: string;
20+
};
21+
}
22+
23+
export type StatusCheckResult =
24+
| { status: 'operational' }
25+
| { status: 'degraded'; description: string }
26+
| { status: 'down'; description: string }
27+
| { status: 'unknown'; error: string };
28+
29+
/**
30+
* Check the Anthropic/Claude status page for service health.
31+
* Returns the current status indicator.
32+
*/
33+
export async function checkAnthropicStatus(): Promise<StatusCheckResult> {
34+
try {
35+
const controller = new AbortController();
36+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
37+
38+
const response = await fetch(CLAUDE_STATUS_URL, {
39+
signal: controller.signal,
40+
});
41+
42+
clearTimeout(timeoutId);
43+
44+
if (!response.ok) {
45+
return {
46+
status: 'unknown',
47+
error: `Status page returned ${response.status}`,
48+
};
49+
}
50+
51+
const data = (await response.json()) as ClaudeStatusResponse;
52+
const indicator = data.status.indicator;
53+
const description = data.status.description;
54+
55+
switch (indicator) {
56+
case 'none':
57+
return { status: 'operational' };
58+
case 'minor':
59+
return { status: 'degraded', description };
60+
case 'major':
61+
case 'critical':
62+
return { status: 'down', description };
63+
default:
64+
return { status: 'unknown', error: `Unknown indicator: ${indicator}` };
65+
}
66+
} catch (error) {
67+
if (error instanceof Error && error.name === 'AbortError') {
68+
return { status: 'unknown', error: 'Request timed out' };
69+
}
70+
return {
71+
status: 'unknown',
72+
error: error instanceof Error ? error.message : 'Unknown error',
73+
};
74+
}
75+
}
76+
77+
/**
78+
* Check Anthropic status and handle the result.
79+
* - If down: Show error and exit
80+
* - If degraded: Show warning and ask user to continue
81+
* - If operational or unknown: Continue silently
82+
*
83+
* @returns true if the wizard should continue, false if it should abort
84+
*/
85+
export async function checkAnthropicStatusWithPrompt(
86+
options: { ci?: boolean } = {},
87+
): Promise<boolean> {
88+
const result = await checkAnthropicStatus();
89+
90+
if (result.status === 'down') {
91+
clack.log.error(
92+
`${chalk.red(
93+
'Claude/Anthropic services are currently experiencing issues.',
94+
)}
95+
96+
${chalk.yellow('Status:')} ${result.description}
97+
${chalk.yellow('Status page:')} ${CLAUDE_STATUS_PAGE}
98+
99+
The wizard relies on Claude to make changes to your project.
100+
Please check the status page and try again later.`,
101+
);
102+
return false;
103+
}
104+
105+
if (result.status === 'degraded') {
106+
clack.log.warn(
107+
`${chalk.yellow('Claude/Anthropic services are partially degraded.')}
108+
109+
${chalk.yellow('Status:')} ${result.description}
110+
${chalk.yellow('Status page:')} ${CLAUDE_STATUS_PAGE}
111+
112+
The wizard may not work reliably while services are degraded.`,
113+
);
114+
115+
// In CI mode, continue with a warning
116+
if (options.ci) {
117+
clack.log.info('Continuing in CI mode despite degraded status...');
118+
return true;
119+
}
120+
121+
const shouldContinue = await clack.confirm({
122+
message: 'Do you want to continue anyway?',
123+
initialValue: false,
124+
});
125+
126+
if (clack.isCancel(shouldContinue) || !shouldContinue) {
127+
clack.log.info('Wizard cancelled. Please try again later.');
128+
return false;
129+
}
130+
131+
return true;
132+
}
133+
134+
// For 'operational' or 'unknown' status, continue silently
135+
// We don't want to block users if the status check itself fails
136+
return true;
137+
}

0 commit comments

Comments
 (0)