fix(runner): preserve provider root mode across Docker export

This commit is contained in:
Dotta 2026-09-09 16:35:37 -05:00
parent f98fb8059e
commit 56cf3dbb69
3 changed files with 12 additions and 4 deletions

View File

@ -44,7 +44,9 @@ RUN pnpm --filter @paperclipai/paperclip-runner build:typescript \
# The trusted qualification entry exports the identical canonical provider stage.
FROM scratch AS provider-pack-export
COPY --from=provider-pack-build /provider-pack /
# Keep the pack beneath the exporter-owned outer directory: BuildKit gives
# that directory host-local permissions rather than the source root's mode.
COPY --from=provider-pack-build /provider-pack /provider-pack
# Fleet sandbox base image. Keep this section aligned with
# paperclipai/paperclip-cloud/fleet-sandbox-image/Dockerfile. The only Paperclip

View File

@ -48,12 +48,12 @@ export function buildProviderPack({ workspaceRoot = defaultWorkspaceRoot, output
// Same filesystem as OUTPUT, so successful publication is an atomic rename.
// Docker reads its inputs before this empty directory receives exported files.
const temporaryParent = mkdtempSync(join(dirname(outputRoot), ".paperclip-provider-pack-"));
const exported = join(temporaryParent, "exported"), backup = join(temporaryParent, "previous");
const exportRoot = join(temporaryParent, "exported"), exported = join(exportRoot, "provider-pack"), backup = join(temporaryParent, "previous");
let previousMoved = false, published = false;
try {
const build = run("docker", ["build", "--platform", "linux/amd64", "--target", "provider-pack-export",
"--file", dockerfile, "--build-arg", `PAPERCLIP_RUNNER_SOURCE_REVISION=${revision}`,
"--output", `type=local,dest=${exported}`, workspaceRoot],
"--output", `type=local,dest=${exportRoot}`, workspaceRoot],
{ cwd: workspaceRoot, stdio: "inherit", timeout: 12 * 60 * 1000, killSignal: "SIGTERM" });
if (build.error || build.status !== 0) throw new Error("Canonical provider-pack build failed");
const manifest = verifyProviderPack(exported, { revision, lockSha256: expectedLock });

View File

@ -19,7 +19,12 @@ function fixture(runTest) {
writeFileSync(join(workspaceRoot, "pnpm-lock.yaml"), "different CI resolution");
const calls = [];
function exported(args, tamper) {
const destination = args[args.indexOf("--output") + 1].slice("type=local,dest=".length);
const exportRoot = args[args.indexOf("--output") + 1].slice("type=local,dest=".length);
// BuildKit's local exporter owns the outer directory and creates it 0700.
// The nested pack must retain the mode bound in its integrity manifest.
mkdirSync(exportRoot, { recursive: true, mode: 0o700 });
chmodSync(exportRoot, 0o700);
const destination = join(exportRoot, "provider-pack");
const paths = {
nodeCommand: "node_modules/node/bin/node", productionLock: "pnpm-lock.yaml",
opencodeCommand: "node_modules/.bin/opencode", opencodeExecutable: "node_modules/opencode-ai/bin/opencode.exe",
@ -125,6 +130,7 @@ for (const [name, tamper] of [
["extra dependency", root => writeFileSync(join(root, "node_modules/unexpected"), "extra")],
["lost executable mode", root => chmodSync(join(root, "node_modules/pi/vendor/pi"), 0o644)],
["directory mode", root => chmodSync(join(root, "node_modules/pi/vendor"), 0o700)],
["pack root mode", root => chmodSync(root, 0o700)],
["changed internal symlink", root => { unlinkSync(join(root, "node_modules/pi-link")); symlinkSync(".bin/pi", join(root, "node_modules/pi-link")); }],
["escaping symlink", root => { unlinkSync(join(root, "node_modules/pi-link")); symlinkSync("../../", join(root, "node_modules/pi-link")); }],
["absolute symlink", root => { unlinkSync(join(root, "node_modules/pi-link")); symlinkSync(join(root, "node_modules/pi/vendor/pi"), join(root, "node_modules/pi-link")); }],