Compare commits

...

3 Commits

Author SHA1 Message Date
Chris Farhood fc6351b2bc docs: mark README as abandoned, point to new sandbox plugin
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 08:48:29 -04:00
Chris Farhood cc34e05713 0.2.3 2026-04-30 16:58:40 -04:00
Chris Farhood 9b951c4308 fix(execute): stop wrapping waitForJobCompletion in completionWithGrace
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) <noreply@anthropic.com>
2026-04-30 16:58:39 -04:00
4 changed files with 9 additions and 5 deletions
+2
View File
@@ -1,5 +1,7 @@
# OpenCode (Kubernetes) Paperclip Adapter Plugin # OpenCode (Kubernetes) Paperclip Adapter Plugin
> **⚠️ Abandoned** — This adapter is no longer maintained. Please use the new sandbox plugin instead: **[farhoodlabs/paperclip-plugin-k8s](https://github.com/farhoodlabs/paperclip-plugin-k8s)** (`@farhoodlabs/paperclip-plugin-k8s` on npm).
Paperclip adapter plugin that runs OpenCode agents as isolated Kubernetes Jobs instead of inside the main Paperclip process. Paperclip adapter plugin that runs OpenCode agents as isolated Kubernetes Jobs instead of inside the main Paperclip process.
## Features ## Features
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "paperclip-adapter-opencode-k8s", "name": "paperclip-adapter-opencode-k8s",
"version": "0.2.2", "version": "0.2.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "paperclip-adapter-opencode-k8s", "name": "paperclip-adapter-opencode-k8s",
"version": "0.2.2", "version": "0.2.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@kubernetes/client-node": "^1.0.0", "@kubernetes/client-node": "^1.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "paperclip-adapter-opencode-k8s", "name": "paperclip-adapter-opencode-k8s",
"version": "0.2.2", "version": "0.2.3",
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs", "description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
+4 -2
View File
@@ -470,12 +470,14 @@ async function streamAndAwaitJob(
// Run the file tail and the job-completion poll in parallel so that the // 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 // tail loop has a way to stop: when waitForJobCompletion resolves it sets
// stopSignal.stopped, which lets tailPodLogFile drain and return. // 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) const completionPromise = waitForJobCompletion(namespace, jobName, completionTimeoutMs, kubeconfigPath)
.then((r) => { stopSignal.stopped = true; return r; }); .then((r) => { stopSignal.stopped = true; return r; });
const completionGraced = completionWithGrace(completionPromise, LOG_EXIT_COMPLETION_GRACE_MS);
const [tailSettled, completionSettled] = await Promise.allSettled([ const [tailSettled, completionSettled] = await Promise.allSettled([
tailPodLogFile(podLogPath, { onLog: wrappedOnLog, stopSignal }), tailPodLogFile(podLogPath, { onLog: wrappedOnLog, stopSignal }),
completionGraced, completionPromise,
]); ]);
stdout = tailSettled.status === "fulfilled" ? tailSettled.value : ""; stdout = tailSettled.status === "fulfilled" ? tailSettled.value : "";
if (completionSettled.status === "rejected") { if (completionSettled.status === "rejected") {