-
Notifications
You must be signed in to change notification settings - Fork 177
Sandbox error handling #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||
| { | ||||||
| "permissions": { | ||||||
| "allow": [ | ||||||
| "Task(*)", | ||||||
| "Read(//Users/jkneen/Documents/GitHub/flows/**)", | ||||||
| "Bash(git restore:*)", | ||||||
| "Bash(rm:*)", | ||||||
| "Bash(npm run dev:*)", | ||||||
| "Bash(cat:*)", | ||||||
| "Bash(PGPASSWORD=\"npg_2zcXUvGdrM1A\" /opt/homebrew/opt/libpq/bin/psql -h \"ep-bold-sky-agftkbbb-pooler.c-2.eu-central-1.aws.neon.tech\" -U \"neondb_owner\" -d \"neondb\" -c \"SELECT 1\")", | ||||||
|
||||||
| "Bash(PGPASSWORD=\"npg_2zcXUvGdrM1A\" /opt/homebrew/opt/libpq/bin/psql -h \"ep-bold-sky-agftkbbb-pooler.c-2.eu-central-1.aws.neon.tech\" -U \"neondb_owner\" -d \"neondb\" -c \"SELECT 1\")", | |
| "Bash(PGPASSWORD=\"$DB_PASSWORD\" /opt/homebrew/opt/libpq/bin/psql -h \"ep-bold-sky-agftkbbb-pooler.c-2.eu-central-1.aws.neon.tech\" -U \"neondb_owner\" -d \"neondb\" -c \"SELECT 1\")", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| // Rate limiting configuration | ||
| export const MAX_MESSAGES_PER_DAY = parseInt(process.env.MAX_MESSAGES_PER_DAY || '5', 10) | ||
|
|
||
| // Sandbox configuration (in minutes) | ||
| export const MAX_SANDBOX_DURATION = parseInt(process.env.MAX_SANDBOX_DURATION || '300', 10) | ||
| // Sandbox configuration (in minutes) - Vercel Sandbox API has max 2700000ms (45 minutes) | ||
| // We cap at 40 minutes to have a safety buffer | ||
| export const MAX_SANDBOX_DURATION = Math.min(parseInt(process.env.MAX_SANDBOX_DURATION || '40', 10), 40) | ||
|
||
|
|
||
| // Vercel deployment configuration | ||
| export const VERCEL_DEPLOY_URL = | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,7 +62,9 @@ export async function createSandbox(config: SandboxConfig, logger: TaskLogger): | |
|
|
||
| // Use the specified timeout (maxDuration) for sandbox lifetime | ||
| // keepAlive only controls whether we shutdown after task completion | ||
| const timeoutMs = config.timeout ? parseInt(config.timeout.replace(/\D/g, '')) * 60 * 1000 : 60 * 60 * 1000 // Default 1 hour | ||
| // NOTE: Vercel Sandbox API has a maximum timeout of 2700000ms (45 minutes) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sandbox now has a max timeout of 5h! https://vercel.com/changelog/vercel-sandbox-maximum-duration-extended-to-5-hours |
||
| const configTimeoutMinutes = config.timeout ? parseInt(config.timeout.replace(/\D/g, '')) : 40 | ||
| const timeoutMs = Math.min(configTimeoutMinutes * 60 * 1000, 2700000) // Cap at 45 minutes | ||
|
Comment on lines
+65
to
+67
|
||
|
|
||
| // Create sandbox with proper source configuration | ||
| const sandboxConfig = { | ||
|
|
@@ -81,14 +83,50 @@ export async function createSandbox(config: SandboxConfig, logger: TaskLogger): | |
| resources: { vcpus: config.resources?.vcpus || 4 }, | ||
| } | ||
|
|
||
| await logger.info( | ||
| `Sandbox config: timeout=${timeoutMs}ms (${timeoutMs / 1000 / 60} minutes), runtime=${sandboxConfig.runtime}, vcpus=${sandboxConfig.resources.vcpus}`, | ||
| ) | ||
|
|
||
| // Validate sandbox config values | ||
| if (!process.env.SANDBOX_VERCEL_TEAM_ID || process.env.SANDBOX_VERCEL_TEAM_ID.length === 0) { | ||
| throw new Error('Invalid or missing SANDBOX_VERCEL_TEAM_ID environment variable') | ||
| } | ||
| if (!process.env.SANDBOX_VERCEL_PROJECT_ID || process.env.SANDBOX_VERCEL_PROJECT_ID.length === 0) { | ||
| throw new Error('Invalid or missing SANDBOX_VERCEL_PROJECT_ID environment variable') | ||
| } | ||
| if (!process.env.SANDBOX_VERCEL_TOKEN || process.env.SANDBOX_VERCEL_TOKEN.length === 0) { | ||
| throw new Error('Invalid or missing SANDBOX_VERCEL_TOKEN environment variable') | ||
| } | ||
| if (timeoutMs <= 0 || timeoutMs > 2700000) { | ||
| throw new Error(`Invalid timeout value: ${timeoutMs}ms. Must be between 1 and 2700000ms (45 minutes).`) | ||
|
Comment on lines
+100
to
+101
|
||
| } | ||
|
|
||
| // Call progress callback before sandbox creation | ||
| if (config.onProgress) { | ||
| await config.onProgress(25, 'Validating configuration...') | ||
| } | ||
|
|
||
| let sandbox: Sandbox | ||
| try { | ||
| sandbox = await Sandbox.create(sandboxConfig) | ||
| await logger.info('Initiating Vercel Sandbox creation...') | ||
| await logger.info(`Using team: ${process.env.SANDBOX_VERCEL_TEAM_ID?.substring(0, 8)}...`) | ||
| await logger.info(`Using project: ${process.env.SANDBOX_VERCEL_PROJECT_ID?.substring(0, 8)}...`) | ||
|
|
||
| // Add a timeout for sandbox creation (60 seconds to be reasonable but not infinite) | ||
| const sandboxPromise = Sandbox.create(sandboxConfig) | ||
| const timeoutPromise = new Promise<never>((_, reject) => | ||
| setTimeout( | ||
| () => | ||
| reject( | ||
| new Error( | ||
| 'Sandbox creation timed out after 60 seconds. Check Vercel API credentials and network connectivity.', | ||
| ), | ||
| ), | ||
| 60000, | ||
| ), | ||
| ) | ||
|
|
||
| sandbox = await Promise.race([sandboxPromise, timeoutPromise]) | ||
| await logger.info('Sandbox created successfully') | ||
|
|
||
| // Register the sandbox immediately for potential killing | ||
|
|
@@ -116,16 +154,42 @@ export async function createSandbox(config: SandboxConfig, logger: TaskLogger): | |
|
|
||
| // Check if this is a timeout error | ||
| if (errorMessage?.includes('timeout') || errorCode === 'ETIMEDOUT' || errorName === 'TimeoutError') { | ||
| await logger.error(`Sandbox creation timed out after 5 minutes`) | ||
| await logger.error(`Sandbox creation timed out`) | ||
| await logger.error(`This usually happens when the repository is large or has many dependencies`) | ||
| throw new Error('Sandbox creation timed out. Try with a smaller repository or fewer dependencies.') | ||
| } | ||
|
|
||
| await logger.error('Sandbox creation failed') | ||
|
|
||
| // Log detailed error information | ||
| if (errorResponse) { | ||
| await logger.error('HTTP error occurred') | ||
| await logger.error('Error response received') | ||
| const status = errorResponse.status || 'unknown' | ||
| await logger.error(`HTTP error status: ${status}`) | ||
|
|
||
| // Try to extract error details from response | ||
| if (errorResponse.data) { | ||
| try { | ||
| const errorData = | ||
| typeof errorResponse.data === 'string' ? JSON.parse(errorResponse.data) : errorResponse.data | ||
| if (errorData.error?.message) { | ||
| await logger.error(`Vercel API error: ${errorData.error.message}`) | ||
| } | ||
| if (errorData.error?.code) { | ||
| await logger.error(`Error code: ${errorData.error.code}`) | ||
| } | ||
| } catch { | ||
| await logger.error(`Error details: ${JSON.stringify(errorResponse.data)}`) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // For 500 errors, suggest checking Vercel status | ||
| if (errorResponse?.status === 500) { | ||
| await logger.error( | ||
| 'Vercel API returned a 500 error. Please check https://status.vercel.com for service status.', | ||
| ) | ||
| } | ||
|
|
||
| throw error | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this file