From 58c98b2ad09bc90b145e6d4626f8d0213fc999cd Mon Sep 17 00:00:00 2001 From: Andro Marces Date: Sun, 26 Jul 2026 11:32:44 +0800 Subject: [PATCH 1/2] fix(cli): classify a pnpm dlx runtime as ephemeral isEphemeralRuntime recognized npm's _npx cache and the OS temp directory but not pnpm's dlx cache, so `pnpm dlx @richhardry/codor install` reported the install as durable and skipped the copy into ~/.codor/runtime. The registered service was left pointing at the dlx cache, which pnpm can prune or expire independently of the service's lifetime, breaking it later with no configuration change on the user's part. Match on the `dlx` path segment, the same way the existing `_npx` check works, so the durable-runtime copy (and everything gated on it) engages for pnpm's dlx layout too. --- packages/cli/src/runtime-install.spec.ts | 26 +++++++++++++++++++++++- packages/cli/src/runtime-install.ts | 9 ++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/runtime-install.spec.ts b/packages/cli/src/runtime-install.spec.ts index 883f1116..df0ee9fb 100644 --- a/packages/cli/src/runtime-install.spec.ts +++ b/packages/cli/src/runtime-install.spec.ts @@ -25,6 +25,18 @@ const ephemeral: RuntimePaths = { serviceTemplate: join(npxCli, 'packaging/systemd/codor.service'), }; +const dlxCli = join( + HOME, + '.local/share/pnpm/pnpm-cache/dlx/abcd1234ef567890/1706000000000/node_modules/@richhardry/codor/node_modules/@codor/cli', +); +const dlxEphemeral: RuntimePaths = { + root: dlxCli, + layout: 'installed-package', + cliEntrypoint: join(dlxCli, 'dist/index.js'), + staticRoot: join(dlxCli, 'runtime/web'), + serviceTemplate: join(dlxCli, 'packaging/systemd/codor.service'), +}; + const checkoutRoot = join(HOME, 'git/codor'); const checkout: RuntimePaths = { root: checkoutRoot, @@ -95,8 +107,9 @@ function fakeIo(options: { existing?: string; failCopy?: boolean; incompleteCopy // harn:assume setup-installs-durable-per-user-runtime-atomically ref=durable-runtime-install-regression describe('isEphemeralRuntime', () => { - it('flags npx cache and temp paths, not stable locations', () => { + it('flags npx cache, pnpm dlx cache, and temp paths, not stable locations', () => { expect(isEphemeralRuntime(join(HOME, '.npm/_npx/abcd1234'))).toBe(true); + expect(isEphemeralRuntime(join(HOME, '.local/share/pnpm/pnpm-cache/dlx/abcd1234ef567890/1706000000000'))).toBe(true); expect(isEphemeralRuntime(join(tmpdir(), 'x'))).toBe(true); expect(isEphemeralRuntime('/opt/codor')).toBe(false); expect(isEphemeralRuntime(checkoutRoot)).toBe(false); @@ -117,6 +130,17 @@ describe('installDurableRuntime', () => { expect(result.runtime.cliEntrypoint).not.toContain('_npx'); }); + it('stages, validates, and swaps an ephemeral pnpm dlx runtime into ~/.codor/runtime', () => { + const { io, moves } = fakeIo(); + const result = installDurableRuntime({ runtime: dlxEphemeral, dataDir: DATA, version: '0.10.0', io }); + expect(result.action).toBe('installed'); + expect(result.location).toBe(LOCATION); + expect(moves).toContainEqual([`${LOCATION}.staging`, LOCATION]); + // Rooted at the durable location, not the dlx cache the source runtime lived in. + expect(result.runtime.cliEntrypoint).toContain(LOCATION); + expect(result.runtime.cliEntrypoint).not.toContain('dlx'); + }); + it('uses a source checkout in place without copying', () => { const { io, copies } = fakeIo(); const result = installDurableRuntime({ runtime: checkout, dataDir: DATA, version: '0.10.0', io }); diff --git a/packages/cli/src/runtime-install.ts b/packages/cli/src/runtime-install.ts index 7fe57cda..a1b304e2 100644 --- a/packages/cli/src/runtime-install.ts +++ b/packages/cli/src/runtime-install.ts @@ -52,10 +52,15 @@ export function durableRuntimeLocation(dataDir: string): string { return join(dataDir, 'runtime'); } -/** A path is ephemeral when it lives in an npx cache or the OS temp directory. */ +/** A path is ephemeral when it lives in an npx cache, a pnpm dlx cache, or the OS temp directory. */ export function isEphemeralRuntime(path: string): boolean { const temp = tmpdir(); - return path.includes(`${sep}_npx${sep}`) || path === temp || path.startsWith(temp + sep); + return ( + path.includes(`${sep}_npx${sep}`) || + path.includes(`${sep}dlx${sep}`) || + path === temp || + path.startsWith(temp + sep) + ); } function installedWrapperPackageJson(location: string): string { From beee2d60964eab81e0dcdb3733ed3555f0b4cb14 Mon Sep 17 00:00:00 2001 From: Andro Marces Date: Sun, 26 Jul 2026 11:46:43 +0800 Subject: [PATCH 2/2] docs(cli): mention the pnpm dlx cache in resolveInstallSource's docstring Codex review on PR 6 caught that this comment still described ephemeral installs as only npx cache / temp dir, missing the dlx case isEphemeralRuntime now also matches. --- packages/cli/src/runtime-install.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/runtime-install.ts b/packages/cli/src/runtime-install.ts index a1b304e2..43993fb5 100644 --- a/packages/cli/src/runtime-install.ts +++ b/packages/cli/src/runtime-install.ts @@ -70,7 +70,8 @@ function installedWrapperPackageJson(location: string): string { // harn:assume setup-installs-durable-per-user-runtime-atomically ref=durable-runtime-install /** The self-contained module tree the running CLI resolves against, and whether * it is already durable (a source checkout or a stable install) or ephemeral - * (an npx cache / temp dir that must be copied before a service points at it). */ + * (an npx cache, a pnpm dlx cache, or a temp dir that must be copied before a + * service points at it). */ export function resolveInstallSource(runtime: RuntimePaths): { installRoot: string; nodeModules: string; durable: boolean } { if (runtime.layout === 'source-checkout') { return { installRoot: runtime.root, nodeModules: join(runtime.root, 'node_modules'), durable: true };