From a374b492d35f94bb6083567668687fe63e332417 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 29 Aug 2026 17:46:30 -0700 Subject: [PATCH 1/4] Add tests for seed git hook isolation --- packages/hub-client/src/workflow-push.test.ts | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/hub-client/src/workflow-push.test.ts b/packages/hub-client/src/workflow-push.test.ts index 0315c45ee..b8165e5cc 100644 --- a/packages/hub-client/src/workflow-push.test.ts +++ b/packages/hub-client/src/workflow-push.test.ts @@ -7,13 +7,13 @@ // re-seed force-repoints `main` to the canonical content rather than // dying on divergent history it never asked to reconcile. import { describe, expect, test } from "bun:test"; -import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createGitWorkflowPusher } from "./workflow-push"; async function git(args: string[], cwd: string): Promise { - const child = Bun.spawn(["git", ...args], { + const child = Bun.spawn(["git", "-c", "core.hooksPath=", ...args], { cwd, stdout: "pipe", stderr: "pipe", @@ -117,4 +117,50 @@ describe("createGitWorkflowPusher", () => { await rm(work, { recursive: true, force: true }); } }); + + test("commits the seed tree even when the operator's inherited hooksPath would reject seed@workbench.localhost", async () => { + const work = await mkdtemp(join(tmpdir(), "workflow-push-hooks-")); + const previousGlobal = process.env.GIT_CONFIG_GLOBAL; + try { + const hooksDir = join(work, "hooks"); + await mkdir(hooksDir); + const hook = join(hooksDir, "commit-msg"); + await writeFile( + hook, + `#!/bin/sh +author="\${GIT_AUTHOR_EMAIL:-}" +if [ "\$author" = "seed@workbench.localhost" ]; then + echo "commit blocked: author must be listed in allowed-emails" >&2 + exit 1 +fi +`, + "utf-8", + ); + await chmod(hook, 0o755); + const globalConfig = join(work, "gitconfig"); + await writeFile(globalConfig, `[core]\nhooksPath = ${hooksDir}\n`, "utf-8"); + process.env.GIT_CONFIG_GLOBAL = globalConfig; + + const remoteDir = join(work, "remote.git"); + await git(["init", "--bare", "--initial-branch=main", remoteDir], work); + + const pusher = createGitWorkflowPusher(); + const outcome = await pusher({ + remoteUrl: `file://${remoteDir}`, + tokenSecret: "unused-for-file-transport", + workflowJson: '{"v":1}', + packageName: "@workbench-seed/test", + }); + + expect(outcome.outcome).toBe("pushed"); + expect(outcome.commitSha).toMatch(/^[0-9a-f]{40}$/); + } finally { + if (previousGlobal === undefined) { + delete process.env.GIT_CONFIG_GLOBAL; + } else { + process.env.GIT_CONFIG_GLOBAL = previousGlobal; + } + await rm(work, { recursive: true, force: true }); + } + }); }); From b8c550c5c42e7d1ab47ee94dd78b2785135d5705 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 29 Aug 2026 17:46:45 -0700 Subject: [PATCH 2/4] Isolate seed workflow git from operator hooks --- packages/hub-client/src/workflow-push.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hub-client/src/workflow-push.ts b/packages/hub-client/src/workflow-push.ts index f9aa74bc6..30fec1e7d 100644 --- a/packages/hub-client/src/workflow-push.ts +++ b/packages/hub-client/src/workflow-push.ts @@ -31,7 +31,7 @@ async function runGit( cwd: string, env: Record, ): Promise<{ code: number; output: string }> { - const child = Bun.spawn(["git", ...args], { + const child = Bun.spawn(["git", "-c", "core.hooksPath=", ...args], { cwd, env: { ...process.env, ...env }, stdout: "pipe", @@ -128,7 +128,7 @@ export function createGitWorkflowPusher(): WorkflowPusher { { label: "stage", args: ["add", ...Object.keys(tree)] }, { label: "commit", - args: ["commit", "-m", "Deploy the default workflow definition"], + args: ["commit", "--no-verify", "-m", "Deploy the default workflow definition"], }, { // Forced deliberately: this asset repo is seed-owned (this From 072ac74085cd8a95f32a1729d9453da30510e206 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 29 Aug 2026 17:53:25 -0700 Subject: [PATCH 3/4] Format seed git hook isolation --- packages/hub-client/src/workflow-push.test.ts | 15 +++++++++++++-- packages/hub-client/src/workflow-push.ts | 7 ++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/hub-client/src/workflow-push.test.ts b/packages/hub-client/src/workflow-push.test.ts index b8165e5cc..74a1b4bd0 100644 --- a/packages/hub-client/src/workflow-push.test.ts +++ b/packages/hub-client/src/workflow-push.test.ts @@ -7,7 +7,14 @@ // re-seed force-repoints `main` to the canonical content rather than // dying on divergent history it never asked to reconcile. import { describe, expect, test } from "bun:test"; -import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { + chmod, + mkdir, + mkdtemp, + readFile, + rm, + writeFile, +} from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createGitWorkflowPusher } from "./workflow-push"; @@ -138,7 +145,11 @@ fi ); await chmod(hook, 0o755); const globalConfig = join(work, "gitconfig"); - await writeFile(globalConfig, `[core]\nhooksPath = ${hooksDir}\n`, "utf-8"); + await writeFile( + globalConfig, + `[core]\nhooksPath = ${hooksDir}\n`, + "utf-8", + ); process.env.GIT_CONFIG_GLOBAL = globalConfig; const remoteDir = join(work, "remote.git"); diff --git a/packages/hub-client/src/workflow-push.ts b/packages/hub-client/src/workflow-push.ts index 30fec1e7d..4f4b458b9 100644 --- a/packages/hub-client/src/workflow-push.ts +++ b/packages/hub-client/src/workflow-push.ts @@ -128,7 +128,12 @@ export function createGitWorkflowPusher(): WorkflowPusher { { label: "stage", args: ["add", ...Object.keys(tree)] }, { label: "commit", - args: ["commit", "--no-verify", "-m", "Deploy the default workflow definition"], + args: [ + "commit", + "--no-verify", + "-m", + "Deploy the default workflow definition", + ], }, { // Forced deliberately: this asset repo is seed-owned (this From 22dffd13bae3cfb2e2f7f157dfae516d3d6139d9 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 29 Aug 2026 18:05:22 -0700 Subject: [PATCH 4/4] Drop useless escapes in seed hook isolation test --- packages/hub-client/src/workflow-push.test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/hub-client/src/workflow-push.test.ts b/packages/hub-client/src/workflow-push.test.ts index 74a1b4bd0..7b73246b6 100644 --- a/packages/hub-client/src/workflow-push.test.ts +++ b/packages/hub-client/src/workflow-push.test.ts @@ -134,13 +134,15 @@ describe("createGitWorkflowPusher", () => { const hook = join(hooksDir, "commit-msg"); await writeFile( hook, - `#!/bin/sh -author="\${GIT_AUTHOR_EMAIL:-}" -if [ "\$author" = "seed@workbench.localhost" ]; then - echo "commit blocked: author must be listed in allowed-emails" >&2 - exit 1 -fi -`, + [ + "#!/bin/sh", + 'author="${GIT_AUTHOR_EMAIL:-}"', + 'if [ "$author" = "seed@workbench.localhost" ]; then', + ' echo "commit blocked: author must be listed in allowed-emails" >&2', + " exit 1", + "fi", + "", + ].join("\n"), "utf-8", ); await chmod(hook, 0o755);