Skip to content

Commit d842ce1

Browse files
authored
fix: surface 429 rate-limit errors in e2e tests and CLI (vercel#1309)
Multiple layered issues caused encryption key 429 errors to produce confusing, unrelated-looking test failures: - awaitCommand() in e2e utils now rejects on non-zero exit codes instead of silently resolving (the most critical fix) - cliInspectJson/cliHealthJson throw on empty stdout instead of falling back to '{}' - maybeDecryptFields re-throws HTTP errors instead of silently falling back to encrypted placeholders - fetchRunKey includes response body and status text in errors - Added 429 to CLI status text map
1 parent 4a6ddd8 commit d842ce1

6 files changed

Lines changed: 59 additions & 7 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@workflow/cli": patch
3+
---
4+
5+
Surface HTTP errors (e.g. 429 rate limit) from encryption key fetch instead of silently falling back to encrypted placeholders. Add 429 to the status text map.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@workflow/world-vercel": patch
3+
---
4+
5+
Include response body and status text in fetchRunKey error message for better debuggability of rate limit and server errors.

packages/cli/src/lib/inspect/hydration.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,19 @@ async function maybeDecryptFields<
229229
result.eventData = eventData;
230230
}
231231
} catch (err) {
232+
const message = err instanceof Error ? err.message : String(err);
233+
234+
// If the key fetch failed due to an HTTP error (e.g. 429 rate limit,
235+
// 500 server error), re-throw so the caller surfaces a clear failure
236+
// instead of silently showing encrypted placeholders.
237+
if (message.includes('HTTP ')) {
238+
throw err;
239+
}
240+
232241
// Decryption failed (bad key, corrupted ciphertext, etc.) — fall back
233242
// to showing encrypted placeholders instead of crashing the CLI.
234243
const { logger } = await import('../config/log.js');
235-
logger.warn(
236-
`Decryption failed for resource ${runId}: ${err instanceof Error ? err.message : String(err)}`
237-
);
244+
logger.warn(`Decryption failed for resource ${runId}: ${message}`);
238245
}
239246

240247
return result;

packages/cli/src/lib/inspect/output.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ const getStatusText = (status: number): string => {
238238
401: 'Unauthorized',
239239
403: 'Forbidden',
240240
404: 'Not Found',
241+
429: 'Too Many Requests',
241242
500: 'Internal Server Error',
242243
502: 'Bad Gateway',
243244
503: 'Service Unavailable',

packages/core/e2e/utils.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,19 @@ const awaitCommand = async (
165165
}
166166

167167
child.on('error', (err) => reject(err));
168-
child.on('close', () => {
168+
child.on('close', (code, signal) => {
169+
if (code !== 0) {
170+
const exitReason = signal
171+
? `killed by signal ${signal}`
172+
: `exited with code ${code}`;
173+
const errorMessage = [
174+
`CLI command failed (${exitReason}): ${command} ${args.join(' ')}`,
175+
stderr ? `\n--- stderr ---\n${stderr}` : '',
176+
stdout ? `\n--- stdout ---\n${stdout}` : '',
177+
].join('');
178+
reject(new Error(errorMessage));
179+
return;
180+
}
169181
resolve({ stdout, stderr });
170182
});
171183
}
@@ -202,9 +214,17 @@ export const cliInspectJson = async (args: string) => {
202214
],
203215
cliAppPath
204216
);
217+
if (!result.stdout.trim()) {
218+
throw new Error(
219+
[
220+
'CLI produced no stdout output (expected JSON)',
221+
result.stderr ? `\n--- stderr ---\n${result.stderr}` : '',
222+
].join('')
223+
);
224+
}
205225
try {
206226
console.log('Result:', result.stdout);
207-
const json = JSON.parse(result.stdout || '{}');
227+
const json = JSON.parse(result.stdout);
208228
return { json, stdout: result.stdout, stderr: result.stderr };
209229
} catch (err) {
210230
console.error('Stdout:', result.stdout);
@@ -267,9 +287,17 @@ export const cliHealthJson = async (options?: {
267287
45_000,
268288
envOverrides
269289
);
290+
if (!result.stdout.trim()) {
291+
throw new Error(
292+
[
293+
'CLI health check produced no stdout output (expected JSON)',
294+
result.stderr ? `\n--- stderr ---\n${result.stderr}` : '',
295+
].join('')
296+
);
297+
}
270298
try {
271299
console.log('Health check result:', result.stdout);
272-
const json = JSON.parse(result.stdout || '{}');
300+
const json = JSON.parse(result.stdout);
273301
return { json, stdout: result.stdout, stderr: result.stderr };
274302
} catch (err) {
275303
console.error('Stdout:', result.stdout);

packages/world-vercel/src/encryption.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,14 @@ export async function fetchRunKey(
127127
);
128128

129129
if (!response.ok) {
130+
let body: string;
131+
try {
132+
body = await response.text();
133+
} catch {
134+
body = '<unable to read response body>';
135+
}
130136
throw new Error(
131-
`Failed to fetch run key for ${runId} (deployment ${deploymentId}): HTTP ${response.status}`
137+
`Failed to fetch run key for ${runId} (deployment ${deploymentId}): HTTP ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`
132138
);
133139
}
134140

0 commit comments

Comments
 (0)