From 2bd8107f1d1ab1bd3a2e727bccb6bf98f751294e Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sat, 25 Apr 2026 10:11:47 +0000 Subject: [PATCH] fix: skills not bundled and resumeLastSession ignored (FAR-56, FAR-57) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs prevented skill content from reaching K8s Job prompts, and resumeLastSession: false was silently ignored. Skills fix (execute.ts, FAR-57): - Add /paperclip/.claude/skills as additional candidate to readPaperclipRuntimeSkillEntries — the relative candidates in adapter-utils don't resolve to the PVC-mounted skills home - Read entry.source/SKILL.md instead of entry.source (which is a directory path); fall back to source directly for file-based entries - Mock readPaperclipRuntimeSkillEntries in execute.test.ts to prevent real SKILL.md reads from delaying fake-timer registration Session fix (job-manifest.ts, FAR-56): - Gate --session flag on asBoolean(config.resumeLastSession, true) so setting resumeLastSession: false actually stops session resumption - Default true preserves existing behaviour for agents without config Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- src/server/execute.test.ts | 7 +++++++ src/server/execute.ts | 15 +++++++++++++-- src/server/job-manifest.ts | 4 +++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 417b365..64f86ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "paperclip-adapter-opencode-k8s", - "version": "0.1.21", + "version": "0.1.22", "description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs", "license": "MIT", "type": "module", diff --git a/src/server/execute.test.ts b/src/server/execute.test.ts index 485c514..80a7ebc 100644 --- a/src/server/execute.test.ts +++ b/src/server/execute.test.ts @@ -16,6 +16,13 @@ vi.mock("./job-manifest.js", () => ({ LARGE_PROMPT_THRESHOLD_BYTES: 256 * 1024, })); +// Prevent skill loading from reading real SKILL.md files during tests — the +// real filesystem read delays timer registration and breaks fake-timer tests. +vi.mock("@paperclipai/adapter-utils/server-utils", async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, readPaperclipRuntimeSkillEntries: vi.fn().mockResolvedValue([]) }; +}); + const MOCK_SELF_POD = { namespace: "test-ns", image: "test-image:latest", diff --git a/src/server/execute.ts b/src/server/execute.ts index a2c7165..09b68e7 100644 --- a/src/server/execute.ts +++ b/src/server/execute.ts @@ -2,6 +2,7 @@ import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclip import { inferOpenAiCompatibleBiller, redactHomePathUserSegments } from "@paperclipai/adapter-utils"; import { asString, asNumber, asBoolean, parseObject, readPaperclipRuntimeSkillEntries, resolvePaperclipDesiredSkillNames } from "@paperclipai/adapter-utils/server-utils"; import { readFile } from "node:fs/promises"; +import path from "node:path"; import { parseOpenCodeJsonl, isOpenCodeUnknownSessionError, @@ -928,14 +929,24 @@ export async function execute(ctx: AdapterExecutionContext): Promise e.key === key); if (entry?.source) { try { - const text = (await readFile(entry.source, "utf-8")).trim(); + // entry.source from listPaperclipSkillEntries is a directory; read SKILL.md from it. + // Fall back to reading entry.source directly for file-based paperclipRuntimeSkills entries. + let text: string; + try { + text = (await readFile(path.join(entry.source, "SKILL.md"), "utf-8")).trim(); + } catch { + text = (await readFile(entry.source, "utf-8")).trim(); + } if (text) skillTexts.push(text); } catch { // skip unreadable skill files — non-fatal diff --git a/src/server/job-manifest.ts b/src/server/job-manifest.ts index bfea102..8407596 100644 --- a/src/server/job-manifest.ts +++ b/src/server/job-manifest.ts @@ -286,7 +286,9 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult { // Build opencode CLI args const opencodeArgs = ["run", "--format", "json"]; - if (runtimeSessionId) opencodeArgs.push("--session", runtimeSessionId); + // resumeLastSession defaults to true (preserve existing behaviour); set to false to start fresh. + const resumeLastSession = asBoolean(config.resumeLastSession, true); + if (runtimeSessionId && resumeLastSession) opencodeArgs.push("--session", runtimeSessionId); if (model) opencodeArgs.push("--model", model); if (variant) opencodeArgs.push("--variant", variant); if (extraArgs.length > 0) opencodeArgs.push(...extraArgs);