From a260b5fa02ee43de9be2d2a0cd58eb2904bacae3 Mon Sep 17 00:00:00 2001 From: Russell Date: Tue, 11 Nov 2025 16:34:05 -0500 Subject: [PATCH] Handle cancellation and timeout recovery (#37) * Enable binary downloads on timeout/cancellation When code execution times out or is cancelled, the compiled binary may still be available. This change ensures displayExecutionResults() is called for timeout/cancelled jobs, allowing users to download the binary artifact even when execution doesn't complete normally. * Fix partial output display for timeout/cancellation Previous commit broke partial output display by passing job.result directly to displayExecutionResults(), but for timeout/cancelled jobs the output is in partial_output field, not stdout. Now properly maps partial_output to stdout before displaying, so users see both the error message and any output that was captured before timeout/cancellation, plus binary downloads if available. * Add debugging for missing artifact on timeout/cancel Check multiple possible locations for artifact: - job.artifact (top level) - job.result.artifact (nested) Add console logging to see full job structure when timeout/cancel occurs so we can understand why the binary isn't appearing. * Try fetching artifact from separate endpoint on timeout/cancel When timeout/cancel occurs, the artifact isn't in the job response. Try fetching from /jobs/{job_id}/artifact endpoint as a fallback. This explores whether the executor service has a separate artifact endpoint that we can use to retrieve compiled binaries even when execution is cancelled or times out. * Remove debug logging, document artifact limitation Removed console.log debugging statements now that we've confirmed the executor service doesn't include artifacts in timeout/cancelled responses and doesn't have a /jobs/{job_id}/artifact endpoint. Kept the artifact fetching code with comments for future compatibility if the executor service adds this feature. Current limitation: Binary downloads only work for completed executions, not for timeout/cancelled ones. The binary exists but the executor service doesn't return it. * Try multiple artifact endpoint patterns for timeout/cancel When artifact isn't in the job response, try fetching from: - /artifacts/{job_id} - /jobs/{job_id}/artifact - /jobs/{job_id}/download - /jobs/{job_id}/binary - /download/{job_id} - /binary/{job_id} Handles both JSON responses and direct binary responses. Logs each attempt to console so we can see which endpoint (if any) works. * Revert endpoint searching - artifact should be in /jobs/{id} According to OpenAPI spec, there are no separate artifact endpoints. The artifact should be included in GET /jobs/{id} response for ALL job statuses (completed, cancelled, timeout). Current limitation: The executor service only includes result.artifact for "completed" status, not for "cancelled" or "timeout" status. The frontend code is correct - it checks job.artifact and job.result.artifact. The issue is the executor service needs to include the artifact in cancelled/timeout responses. * Add debug logging for cancelled/timeout artifact checks Since the executor service was supposedly patched to include artifacts in GET /jobs/{id} responses even for cancelled/timeout jobs, add detailed logging to verify: 1. What the full job response looks like 2. Whether artifact is at job.artifact or job.result.artifact 3. Artifact details if found This will help determine if the patch is deployed and working. * Add test-artifact Makefile target for testing executor API Tests binary artifact retrieval from code executor service: - Compiles C code with return_artifact=true - Extracts base64 artifact from response - Decodes and executes the binary Can test against different URLs: make test-artifact URL=https://code.ai.unturf.com Tested against production and confirmed: - Artifacts ARE included for completed jobs - Artifacts are NOT included for cancelled/timeout jobs (even with return_artifact=true). Exit code 137 indicates SIGKILL. * Document confirmed limitation - no artifacts for cancelled jobs Tested against production executor API (make test-artifact) and confirmed: - Cancelled jobs return exit_code 137 (SIGKILL) - NO artifact field in response (neither job.artifact nor job.result.artifact) - Artifacts only returned for fully completed jobs Code still checks for artifacts in case this limitation is fixed in the future, but currently binary downloads will not work for cancelled/timeout executions. To fix: Executor service needs to include compiled binary in response even when execution is killed (compilation succeeded). --------- Co-authored-by: Claude --- Makefile | 39 +++++++++++++++++++++++++++++++++++++- templates/chat.html | 46 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 3fb13b2..285526d 100644 --- a/Makefile +++ b/Makefile @@ -242,4 +242,41 @@ test-info: @echo "" @echo "🎯 Key Test Commands:" @echo " make test - Run all tests" - @echo " make validate-yaml - Validate all YAML files" \ No newline at end of file + @echo " make validate-yaml - Validate all YAML files" +# ============================================================================ +# CODE EXECUTOR API TESTING +# ============================================================================ + +# Test artifact retrieval - compile C code, get base64 binary, decode and test execution +# URL can be overridden: make test-artifact URL=https://code.ai.unturf.com +.PHONY: test-artifact +test-artifact: + $(eval URL ?= http://127.0.0.1:8080) + @echo "==========================================" + @echo "Testing Binary Artifact Retrieval" + @echo "==========================================" + @echo "API: $(URL)" + @echo "" + @echo "Step 1: Compiling C code and retrieving base64 binary..." + @curl -s -X POST $(URL)/execute \ + -H "Content-Type: application/json" \ + -d '{"language": "c", "code": "#include \nint main() { printf(\"Hello from artifact!\\n\"); return 0; }", "return_artifact": true}' \ + | jq -r '.stdout.artifact.data' > /tmp/artifact.b64 + @echo "✓ Base64 artifact saved to /tmp/artifact.b64" + @echo " Size: $$(wc -c < /tmp/artifact.b64) bytes (base64)" + @echo "" + @echo "Step 2: Decoding base64 to binary..." + @base64 -d /tmp/artifact.b64 > /tmp/artifact_binary + @chmod +x /tmp/artifact_binary + @echo "✓ Binary decoded to /tmp/artifact_binary" + @echo " Size: $$(wc -c < /tmp/artifact_binary) bytes (ELF binary)" + @echo "" + @echo "Step 3: Verifying ELF binary..." + @file /tmp/artifact_binary + @echo "" + @echo "Step 4: Executing binary..." + @/tmp/artifact_binary + @echo "" + @echo "✓ Artifact test complete!" + @echo "" + @echo "Cleanup: rm /tmp/artifact.b64 /tmp/artifact_binary" diff --git a/templates/chat.html b/templates/chat.html index 13a6eb2..b2b32a7 100644 --- a/templates/chat.html +++ b/templates/chat.html @@ -1492,16 +1492,50 @@ async function executeCodeBlock(code, blockElement, playButton) { break; } - // timeout or cancelled + // timeout or cancelled - display results and artifact if available const errorMsg = job.result?.error || 'Execution failed'; const partialOutput = job.result?.partial_output; - let outputHtml = `
${escapeHtml(errorMsg)}
`; - if (partialOutput) { - outputHtml += '
Partial output before timeout:
'; - outputHtml += `
${escapeHtml(partialOutput)}
`; + // CONFIRMED via testing (make test-artifact): Executor service does NOT + // include artifact field in GET /jobs/{id} response for cancelled jobs + // (exit_code 137 = SIGKILL), even when return_artifact=true was requested. + // Artifacts only included for fully completed jobs (exit_code 0). + // + // The code below checks for artifact anyway in case this gets fixed in the + // future, but currently it will always be undefined for cancelled/timeout. + const artifact = job.artifact || job.result?.artifact; + + // Build result object that displayExecutionResults can understand + // For timeout/cancelled, partial_output contains the output before timeout + if (job.result) { + const resultForDisplay = { + stdout: partialOutput || job.result.stdout || '', + stderr: job.result.stderr || '', + artifact: artifact + }; + displayExecutionResults(resultForDisplay, resultsContainer, language); + + // Prepend error message to the results + const errorDiv = document.createElement('div'); + errorDiv.style.color = 'var(--text-error)'; + errorDiv.style.fontWeight = 'bold'; + errorDiv.style.marginBottom = '8px'; + errorDiv.textContent = errorMsg; + resultsContainer.insertBefore(errorDiv, resultsContainer.firstChild); + + // Add note if there was partial output + if (partialOutput) { + const partialNote = document.createElement('div'); + partialNote.style.color = 'var(--text-muted)'; + partialNote.style.fontSize = '12px'; + partialNote.style.marginBottom = '8px'; + partialNote.textContent = '(Output before timeout/cancellation)'; + resultsContainer.insertBefore(partialNote, resultsContainer.children[1]); + } + } else { + // No result object at all, just show error + resultsContainer.innerHTML = `
${escapeHtml(errorMsg)}
`; } - resultsContainer.innerHTML = outputHtml; break; }