Compare commits

...

2 Commits

Author SHA1 Message Date
Chris Farhood fe6bc0c2d6 fix(config): remove instructionsFilePath from UI config schema
Handled by the framework via instructionsPathKey/supportsInstructionsBundle.
Surfacing it as an editable field in the config schema was redundant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:25:33 -04:00
Chris Farhood 2d057f085d refactor: remove PAPERCLIP_DEV_API_KEY runtime hack throughout
Cancel poll now uses ctx.authToken exclusively. Remove forwarding of
PAPERCLIP_DEV_API_KEY into job pods and all associated tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 07:24:14 -04:00
6 changed files with 4 additions and 58 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "paperclip-adapter-opencode-k8s",
"version": "0.1.36",
"version": "0.1.38",
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
"license": "MIT",
"type": "module",
-7
View File
@@ -11,13 +11,6 @@ export function getConfigSchema(): AdapterConfigSchema {
hint: "Provider-specific reasoning/profile variant passed as --variant",
group: "Core",
},
{
key: "instructionsFilePath",
label: "Instructions File Path",
type: "text",
hint: "Absolute path to a markdown file (e.g. AGENTS.md) prepended as system instructions before the task prompt",
group: "Core",
},
{
key: "dangerouslySkipPermissions",
label: "Skip Permission Checks",
-37
View File
@@ -884,7 +884,6 @@ describe("execute — external cancel polling", () => {
vi.useRealTimers();
vi.unstubAllGlobals();
delete process.env.PAPERCLIP_API_URL;
delete process.env.PAPERCLIP_DEV_API_KEY;
});
it("returns errorCode=cancelled and deletes job when issue status is cancelled", async () => {
@@ -970,42 +969,6 @@ describe("execute — external cancel polling", () => {
expect(result.errorCode).toBeUndefined();
expect(result.exitCode).toBe(0);
});
it("uses PAPERCLIP_DEV_API_KEY over ctx.authToken when set", async () => {
vi.useFakeTimers();
process.env.PAPERCLIP_API_URL = "http://test-api";
process.env.PAPERCLIP_DEV_API_KEY = "dev-override-key";
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ status: "cancelled" }),
});
vi.stubGlobal("fetch", fetchMock);
let jobDeleted = false;
const batchApi = makeBatchApi();
batchApi.deleteNamespacedJob.mockImplementation(() => { jobDeleted = true; return Promise.resolve({}); });
batchApi.readNamespacedJob.mockImplementation(() => {
if (jobDeleted) return Promise.reject(Object.assign(new Error("not found"), { statusCode: 404 }));
return Promise.resolve({ status: { conditions: [] } });
});
vi.mocked(getBatchApi).mockReturnValue(batchApi as unknown as ReturnType<typeof getBatchApi>);
const ctx = makeCtx({}, { issueId: "issue-test-456" }, "ctx-auth-token");
const executePromise = execute(ctx);
for (let i = 0; i < 20; i++) {
await vi.advanceTimersByTimeAsync(1_000);
}
await executePromise;
expect(fetchMock).toHaveBeenCalledWith(
"http://test-api/api/issues/issue-test-456",
expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer dev-override-key" }) }),
);
});
});
describe("execute — large-prompt Secret path", () => {
+1 -3
View File
@@ -569,9 +569,7 @@ async function streamAndAwaitJob(
await new Promise<void>((resolve) => setTimeout(resolve, KEEPALIVE_INTERVAL_MS));
if (logStopSignal.stopped || cancelSignal.cancelled) break;
try {
// Prefer PAPERCLIP_DEV_API_KEY if set (dev override), otherwise use
// the per-run authToken issued by Paperclip for this execution.
const apiKey = process.env.PAPERCLIP_DEV_API_KEY ?? ctx.authToken ?? "";
const apiKey = ctx.authToken ?? "";
const resp = await fetch(`${apiUrl}/api/issues/${issueId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
+2 -3
View File
@@ -484,15 +484,14 @@ describe("buildJobManifest — env wiring branches", () => {
expect(env.find((e) => e.name === "PAPERCLIP_API_KEY")?.value).toBe("tok_abc");
});
it("inherits PAPERCLIP_API_URL and PAPERCLIP_DEV_API_KEY from selfPod inheritedEnv", () => {
it("inherits PAPERCLIP_API_URL from selfPod inheritedEnv", () => {
const selfPod = {
...mockSelfPod,
inheritedEnv: { PAPERCLIP_API_URL: "http://api", PAPERCLIP_DEV_API_KEY: "dev_key" },
inheritedEnv: { PAPERCLIP_API_URL: "http://api" },
};
const result = buildJobManifest({ ctx: mockCtx, selfPod });
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
expect(env.find((e) => e.name === "PAPERCLIP_API_URL")?.value).toBe("http://api");
expect(env.find((e) => e.name === "PAPERCLIP_DEV_API_KEY")?.value).toBe("dev_key");
});
});
-7
View File
@@ -173,13 +173,6 @@ function buildEnvVars(
if (selfPod.inheritedEnv.PAPERCLIP_API_URL) {
paperclipEnv.PAPERCLIP_API_URL = selfPod.inheritedEnv.PAPERCLIP_API_URL;
}
// Inherit PAPERCLIP_DEV_API_KEY if set (dev-instance key, distinct from the
// main-instance run JWT in PAPERCLIP_API_KEY). Used by the external cancel
// polling in execute.ts to authenticate against the dev Paperclip instance.
if (selfPod.inheritedEnv.PAPERCLIP_DEV_API_KEY) {
paperclipEnv.PAPERCLIP_DEV_API_KEY = selfPod.inheritedEnv.PAPERCLIP_DEV_API_KEY;
}
// Layer 3: Inherited from Deployment (Bedrock, API keys, etc.)
const merged: Record<string, string> = {
...selfPod.inheritedEnv,