From b8ba45779092879aefc9365aa8c09e3fc80b5899 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 16 Apr 2026 11:29:42 -0400 Subject: [PATCH] fix: don't delete job when returning state-mismatch error to keep UI in sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When waitForJobCompletion threw and the job was still not terminal, we were returning an error but still deleting the job in the finally block. This left the UI holding an error while the job (still alive) would be cleaned up by Kubernetes, causing the next heartbeat to find nothing and think it was safe to retry — spawning a concurrent pod. Now we set skipCleanup=true when returning the mismatch error, so the job is retained and the heartbeat can still find and wait on it. Also removes a duplicate empty-stdout fallback block. Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 4 ++-- package.json | 2 +- src/server/execute.ts | 18 +++++++----------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 814a480..cdf7652 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@farhoodliquor/paperclip-adapter-claude-k8s", - "version": "0.1.15", + "version": "0.1.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@farhoodliquor/paperclip-adapter-claude-k8s", - "version": "0.1.15", + "version": "0.1.16", "license": "MIT", "dependencies": { "@kubernetes/client-node": "^1.0.0", diff --git a/package.json b/package.json index 199b6bf..9140727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@farhoodliquor/paperclip-adapter-claude-k8s", - "version": "0.1.15", + "version": "0.1.16", "description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs", "license": "MIT", "repository": { diff --git a/src/server/execute.ts b/src/server/execute.ts index e958fe4..bb3499c 100644 --- a/src/server/execute.ts +++ b/src/server/execute.ts @@ -379,6 +379,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise | null = null; + // Set when we return a mismatch error so the finally block knows not to + // delete a job that is still alive and the UI is waiting on. + let skipCleanup = false; try { // Wait for pod to be ready for log streaming @@ -444,16 +447,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise