Fix skill injection to use effective CODEX_HOME, not shared home

The previous commit incorrectly used resolveSharedCodexHomeDir() (~/.codex)
but Codex runs with CODEX_HOME set to a per-company managed home under
~/.paperclip/instances/. Skills injected into ~/.codex/skills/ would not
be discoverable by Codex. Now uses effectiveCodexHome directly.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin Foley 2026-03-25 16:04:53 -07:00
parent eeec52ad74
commit 623ab1c3ea
4 changed files with 8 additions and 8 deletions

View file

@ -21,7 +21,7 @@ import {
runChildProcess,
} from "@paperclipai/adapter-utils/server-utils";
import { parseCodexJsonl, isCodexUnknownSessionError } from "./parse.js";
import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir, resolveSharedCodexHomeDir } from "./codex-home.js";
import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir } from "./codex-home.js";
import { resolveCodexDesiredSkillNames } from "./skills.js";
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
@ -135,8 +135,8 @@ async function pruneBrokenUnavailablePaperclipSkillSymlinks(
}
}
function resolveCodexSkillsHome(): string {
return path.join(resolveSharedCodexHomeDir(), "skills");
function resolveCodexSkillsDir(codexHome: string): string {
return path.join(codexHome, "skills");
}
type EnsureCodexSkillsInjectedOptions = {
@ -273,7 +273,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const defaultCodexHome = resolveManagedCodexHomeDir(process.env, agent.companyId);
const effectiveCodexHome = configuredCodexHome ?? preparedManagedCodexHome ?? defaultCodexHome;
await fs.mkdir(effectiveCodexHome, { recursive: true });
const codexSkillsDir = resolveCodexSkillsHome();
const codexSkillsDir = resolveCodexSkillsDir(effectiveCodexHome);
await ensureCodexSkillsInjected(
onLog,
{

View file

@ -31,7 +31,7 @@ async function buildCodexSkillSnapshot(
sourcePath: entry.source,
targetPath: null,
detail: desiredSet.has(entry.key)
? "Will be linked into ~/.codex/skills/ on the next run."
? "Will be linked into $CODEX_HOME/skills/ on the next run."
: null,
required: Boolean(entry.required),
requiredReason: entry.requiredReason ?? null,

View file

@ -210,7 +210,7 @@ describe("codex execute", () => {
"company-1",
"codex-home",
);
const homeSkill = path.join(sharedCodexHome, "skills", "paperclip");
const homeSkill = path.join(isolatedCodexHome, "skills", "paperclip");
await fs.mkdir(workspace, { recursive: true });
await fs.mkdir(sharedCodexHome, { recursive: true });
await fs.writeFile(path.join(sharedCodexHome, "auth.json"), '{"token":"shared"}\n', "utf8");
@ -371,7 +371,7 @@ describe("codex execute", () => {
const capture = JSON.parse(await fs.readFile(capturePath, "utf8")) as CapturePayload;
expect(capture.codexHome).toBe(explicitCodexHome);
expect((await fs.lstat(path.join(sharedCodexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true);
expect((await fs.lstat(path.join(explicitCodexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true);
await expect(fs.lstat(path.join(paperclipHome, "instances", "worktree-1", "codex-home"))).rejects.toThrow();
} finally {
if (previousHome === undefined) delete process.env.HOME;

View file

@ -43,7 +43,7 @@ describe("codex local skill sync", () => {
expect(before.desiredSkills).toContain(paperclipKey);
expect(before.entries.find((entry) => entry.key === paperclipKey)?.required).toBe(true);
expect(before.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("configured");
expect(before.entries.find((entry) => entry.key === paperclipKey)?.detail).toContain("~/.codex/skills/");
expect(before.entries.find((entry) => entry.key === paperclipKey)?.detail).toContain("$CODEX_HOME/skills/");
});
it("does not persist Paperclip skills into CODEX_HOME during sync", async () => {