From 9b951c430869ff91129d9fa60a6bb3460f8d4106 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 30 Apr 2026 16:58:39 -0400 Subject: [PATCH] fix(execute): stop wrapping waitForJobCompletion in completionWithGrace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 30s completionWithGrace was originally a "wait a bit for the job to settle after tail returns" — sequential. When 0.2.0 moved tail and completion into Promise.allSettled to give tail a stop signal, the grace wrapper was kept around the parallel completion poll. That turned the 30s grace into a hard ceiling on the entire run: completionGraced resolves with {timedOut: true} after 30s regardless of how the actual job is doing, which feeds back into jobTimedOut and surfaces to the user as "Timed out after 0s" when timeoutSec is 0 (no configured timeout). Drop the wrapper. Use the bare completionPromise. The tail loop already has a clean stop path via stopSignal.stopped which is set when the real job completion resolves; no separate grace timer is needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/server/execute.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/server/execute.ts b/src/server/execute.ts index 2006624..403d9de 100644 --- a/src/server/execute.ts +++ b/src/server/execute.ts @@ -470,12 +470,14 @@ async function streamAndAwaitJob( // Run the file tail and the job-completion poll in parallel so that the // tail loop has a way to stop: when waitForJobCompletion resolves it sets // stopSignal.stopped, which lets tailPodLogFile drain and return. + // No completionWithGrace wrapper here — wrapping a long-running job poll + // in a 30s grace turns the grace into a hard ceiling and kills runs + // prematurely with "Timed out after 0s" when timeoutSec is 0 (no timeout). const completionPromise = waitForJobCompletion(namespace, jobName, completionTimeoutMs, kubeconfigPath) .then((r) => { stopSignal.stopped = true; return r; }); - const completionGraced = completionWithGrace(completionPromise, LOG_EXIT_COMPLETION_GRACE_MS); const [tailSettled, completionSettled] = await Promise.allSettled([ tailPodLogFile(podLogPath, { onLog: wrappedOnLog, stopSignal }), - completionGraced, + completionPromise, ]); stdout = tailSettled.status === "fulfilled" ? tailSettled.value : ""; if (completionSettled.status === "rejected") {