Skip to content

Conversation

@developerfred
Copy link
Owner

@developerfred developerfred commented Dec 23, 2025

User description

This PR enhances error handling in the useCodeRunner hook to provide better user experience and more robust code execution.

Changes

  • CodeExecutionError class: Custom error class with categorized error types (network, timeout, simulation, unknown)
  • Timeout mechanism: 30-second default timeout to prevent hanging executions
  • Retry logic: Automatic retry with exponential backoff (up to 2 retries)
  • Better error messages: User-friendly error messages with contextual hints
  • Progress tracking: Enhanced progress updates during retries and long operations
  • Error categorization: Automatic detection of network errors, timeouts, and simulation errors

Benefits

  • Prevents UI from hanging on slow or failed network requests
  • Provides clear feedback when operations fail
  • Automatically retries transient failures
  • Better debugging information for users
  • Improved overall reliability of the playground

Closes #42


PR Type

Enhancement, Bug fix


Description

  • Add CodeExecutionError class with error type categorization (network, timeout, simulation, unknown)

  • Implement 30-second timeout mechanism to prevent hanging code executions

  • Add automatic retry logic with exponential backoff (up to 2 retries)

  • Enhance error messages with contextual hints and user-friendly formatting

  • Improve progress tracking and console output with emoji indicators

  • Add configurable timeout and retry options to useCodeRunner hook


Diagram Walkthrough

flowchart LR
  A["Code Execution Request"] --> B["Timeout Promise Race"]
  B --> C{"Execution Success?"}
  C -->|Yes| D["Return Results"]
  C -->|No| E["Categorize Error"]
  E --> F{"Retries Remaining?"}
  F -->|Yes| G["Exponential Backoff"]
  G --> B
  F -->|No| H["Report Final Error"]
  D --> I["User Feedback"]
  H --> I
Loading

File Walkthrough

Relevant files
Enhancement
useCodeRunner.ts
Enhanced error handling with timeout and retry logic         

src/lib/hooks/useCodeRunner.ts

  • Add CodeExecutionError class with error type categorization (network,
    timeout, simulation, unknown)
  • Implement timeout mechanism using Promise.race with configurable
    duration (default 30s)
  • Add retry loop with exponential backoff strategy (up to 2 retries by
    default)
  • Enhance error messages with contextual hints based on error type
  • Improve console output with emoji indicators (🚀, ✅, ❌, 🔄) and progress
    tracking
  • Add timeoutMs and maxRetries options to UseCodeRunnerOptions interface
+120/-38

- Add CodeExecutionError class with error types (network, timeout, simulation, unknown)
- Implement timeout mechanism (30s default) to prevent hanging executions
- Add retry logic with exponential backoff (up to 2 retries by default)
- Better error categorization and user-friendly error messages
- Enhanced progress tracking during execution and retries
- Improved console output with emojis and contextual messages
@developerfred developerfred linked an issue Dec 23, 2025 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Dec 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
papi-simulator Building Building Preview, Comment Dec 23, 2025 6:00pm

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟡
🎫 #42
🟢 Enhance error handling in the useCodeRunner hook to provide more detailed error messages
when code execution fails.
Improve user feedback/UX when code execution fails (clear, actionable feedback).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing cleanup: The new retry/timeout flow never resets isRunning/progress and does not clear the timeout
timer, which can leave the UI stuck running and cause delayed timeout rejections after
success.

Referred Code
// Create a timeout promise
const timeoutPromise = new Promise<never>((_, reject) => {
	setTimeout(() => {
		reject(new CodeExecutionError(
			`Execution timed out after ${timeoutMs}ms`,
			'timeout'
		));
	}, timeoutMs);
});

// Race between simulation and timeout
const simulatedOutputs = await Promise.race([
	simulateCodeExecution(example, network),
	timeoutPromise
]);

setProgress(90);

// Add success message
const successOutputs = [
	...simulatedOutputs,


 ... (clipped 69 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Raw error exposed: User-facing output surfaces lastError.message directly (including in the final “All
attempts failed” message), potentially exposing internal details rather than a generic
safe error message.

Referred Code
let errorType: CodeExecutionError['type'] = 'unknown';
let errorMessage = lastError.message;

// Categorize errors
if (lastError instanceof CodeExecutionError) {
	errorType = lastError.type;
} else if (lastError.message.includes('network') || lastError.message.includes('connection')) {
	errorType = 'network';
	errorMessage = `Network error: ${lastError.message}. Please check your internet connection and try again.`;
} else if (lastError.message.includes('timeout')) {
	errorType = 'timeout';
	errorMessage = `Execution timed out: ${lastError.message}. The operation took too long to complete.`;
} else {
	errorType = 'simulation';
	errorMessage = `Simulation error: ${lastError.message}`;
}

const categorizedError = new CodeExecutionError(errorMessage, errorType, lastError);

// If this was the last attempt, report the error
if (attempt > maxRetries) {


 ... (clipped 25 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured error output: The hook appends free-form error strings (including raw error messages) into outputs,
which may be treated as logs elsewhere and could inadvertently contain sensitive data
depending on upstream error content.

Referred Code
			setOutputs(prev => [...prev, {
				type: "error",
				content: `❌ ${categorizedError.message}${errorType === 'network' ? ' (Check network connection)' : ''}${errorType === 'timeout' ? ' (Try simplifying the code)' : ''}`,
				timestamp: Date.now(),
			}]);

			options.onRunError?.(categorizedError);
		}

		// Wait before retry (exponential backoff)
		if (attempt <= maxRetries) {
			const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
			await new Promise(resolve => setTimeout(resolve, delay));
		}
	}
}

// If we get here, all retries failed
setOutputs(prev => [...prev, {
	type: "error",
	content: `❌ All ${maxRetries + 1} attempts failed. Last error: ${lastError?.message || 'Unknown error'}`,


 ... (clipped 4 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Options not validated: New external options timeoutMs and maxRetries are used without validation/clamping,
allowing negative/NaN/very large values that could create unexpected behavior or resource
exhaustion.

Referred Code
const {
	timeoutMs = 30000, // 30 seconds default
	maxRetries = 2,
} = options;

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Over-engineered error handling for non-failing simulation

The PR adds complex error handling (timeouts, retries) for
simulateCodeExecution, but this function is a simple mock that always succeeds
quickly. This makes the new error handling logic unnecessary and untestable dead
code.

Examples:

src/lib/hooks/useCodeRunner.ts [66-169]
			let attempt = 0;
			let lastError: Error | null = null;

			while (attempt <= maxRetries) {
				try {
					setProgress(20 + (attempt * 10));
					
					if (attempt > 0) {
						setOutputs(prev => [...prev, {
							type: "warning",

 ... (clipped 94 lines)
src/lib/simulation/simulationService.ts [61-92]

Solution Walkthrough:

Before:

// In useCodeRunner.ts
async function runCode(example, network) {
  setIsRunning(true);
  try {
    // Simple simulation call that always succeeds
    const simulatedOutputs = await simulateCodeExecution(example, network);
    setOutputs(simulatedOutputs);
  } catch (error) {
    // Basic error handling
    setOutputs([{ type: "error", content: `Error: ${error.message}` }]);
  } finally {
    setIsRunning(false);
  }
}

After:

// In useCodeRunner.ts
async function runCode(example, network) {
  let attempt = 0;
  while (attempt <= maxRetries) {
    try {
      // Race simulation against a 30-second timeout
      const simulatedOutputs = await Promise.race([
        simulateCodeExecution(example, network), // This mock always resolves in < 2s
        timeoutPromise
      ]);
      setOutputs(simulatedOutputs);
      return; // Success
    } catch (error) {
      // Complex error categorization for network/timeout/simulation errors
      // that will never be triggered by the current mock.
      // Implements exponential backoff for retries.
      attempt++;
    }
  }
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the complex error handling logic is built for a simulateCodeExecution function that is a simple mock and cannot fail, time out, or have network issues, making the new features currently untestable and unused.

High
General
Eliminate duplicate error report

Remove the final error handling block after the while loop to prevent reporting
the same failure twice. The error is already handled within the loop's catch
block on the final attempt.

src/lib/hooks/useCodeRunner.ts [161-168]

-// If we get here, all retries failed
-setOutputs(prev => [...prev, {
-	type: "error",
-	content: `❌ All ${maxRetries + 1} attempts failed. Last error: ${lastError?.message || 'Unknown error'}`,
-	timestamp: Date.now(),
-}]);
+// All retries have been handled in the catch block above; no additional reporting needed here.
 
-options.onRunError?.(lastError || new Error('All retry attempts failed'));
-
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies and resolves a logic bug where a failure is reported twice due to redundant error handling blocks. Removing the duplicate block fixes the bug and simplifies the code.

Medium
Complete and reset progress

Update the UI progress indicator to 100% on successful code execution and then
reset it. This restores the original behavior and provides better user feedback.

src/lib/hooks/useCodeRunner.ts [109-118]

+setProgress(100);
 setOutputs(successOutputs);
 setLastRun({
 	example,
 	network,
 	timestamp: Date.now(),
 });
 options.onRunComplete?.();
+// Reset progress indicator after a brief pause
+setTimeout(() => setProgress(0), 500);
 return;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: This suggestion correctly points out that the progress bar logic was partially lost in the refactoring. Reinstating the progress completion and reset improves the user experience by providing clear visual feedback, which was part of the original implementation's intent.

Low
Possible issue
Clear timeout after race

Clear the setTimeout timer after the Promise.race completes to prevent the
timeout callback from firing unnecessarily.

src/lib/hooks/useCodeRunner.ts [82-95]

+let timeoutId: ReturnType<typeof setTimeout>;
 const timeoutPromise = new Promise<never>((_, reject) => {
-	setTimeout(() => {
+	timeoutId = setTimeout(() => {
 		reject(new CodeExecutionError(
 			`Execution timed out after ${timeoutMs}ms`,
 			'timeout'
 		));
 	}, timeoutMs);
 });
 // Race between simulation and timeout
 const simulatedOutputs = await Promise.race([
 	simulateCodeExecution(example, network),
 	timeoutPromise
 ]);
+clearTimeout(timeoutId);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential memory leak by not clearing the setTimeout. While the proposed improved_code is not fully robust against rejections, it correctly points out the need to clear the timer, which is an important improvement for resource management.

Medium
  • More

@developerfred developerfred merged commit f608e94 into main Dec 23, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Error Handling in Code Runner

2 participants