mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
## Summary Fixes #316 — `hyperframes skills` (and `npx skills add heygen-com/hyperframes`) fails with: ``` ■ Failed to clone repository fatal: active \`post-checkout\` hook found during \`git clone\` └ Installation failed ``` ## Root cause Two layers stacked: 1. **Git 2.45+ refuses to execute hooks during `git clone` by default.** The opt-in is `GIT_CLONE_PROTECTION_ACTIVE=0` — the env-var name is intentionally explicit about the trade-off. 2. **Users who ran `git lfs install` globally have a post-checkout hook registered at `core.hooksPath`.** When the upstream `skills` CLI shells out to `git clone` to fetch a repo's `skills/` directory, git detects the user's LFS hook and aborts. The check fires for **any repo**, regardless of whether the cloned repo uses LFS itself — it's protection against the user's own hooks, not the repo's content. Users who have git-lfs installed (very common) hit this for every clone the `skills` CLI does. ## The fix `hyperframes skills` wraps `npx skills add`. The wrapper now sets `GIT_CLONE_PROTECTION_ACTIVE=0` on the spawned child's env via a single helper (`gitCloneFriendlyEnv`) with a docstring at the call site explaining exactly why. The rest of `process.env` is preserved — proxy settings, extra CA certs, locale, etc. stay untouched. ## What this fix doesn't do (deliberately) This is the **code-path-we-own** fix. The deeper root cause is that the upstream `skills` CLI (vercel-labs/skills) should set this env var when it shells out to `git clone`. That would fix the bug for every user invoking `skills` directly — not just those who route through our wrapper. An upstream issue should be opened separately; not landing it as part of this PR. ## Users who call `npx skills add` directly Documented in the new troubleshooting subsection: set the env var manually. ```bash GIT_CLONE_PROTECTION_ACTIVE=0 npx skills add heygen-com/hyperframes ``` ## Tests `packages/cli/src/commands/skills.test.ts` — 2 cases: - Every spawned child has `GIT_CLONE_PROTECTION_ACTIVE=0` - The rest of `process.env` is preserved (not a wiped env) Uses `vi.mock` on `node:child_process` because ESM doesn't allow live-module `vi.spyOn` on re-exported bindings. ## Docs `docs/packages/cli.mdx` — new **Troubleshooting** subsection under the `skills` command. Explains both the automatic fix (`hyperframes skills` users are already covered) and the manual workaround (`npx skills add …` users). ## Closes - #316
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// ESM forbids `vi.spyOn` on live module exports, so we mock
|
||||
// `node:child_process` at the loader level and inspect the spawned
|
||||
// child's env.
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
type SpawnCall = {
|
||||
command: string;
|
||||
args: ReadonlyArray<string>;
|
||||
env: NodeJS.ProcessEnv | undefined;
|
||||
};
|
||||
|
||||
const state: { calls: SpawnCall[] } = { calls: [] };
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
execFileSync: vi.fn(() => Buffer.from("11.0.0")),
|
||||
spawn: vi.fn(
|
||||
(command: string, args: ReadonlyArray<string>, opts?: { env?: NodeJS.ProcessEnv }) => {
|
||||
state.calls.push({ command, args, env: opts?.env });
|
||||
const fake = new EventEmitter();
|
||||
setImmediate(() => fake.emit("close", 0, null));
|
||||
return fake;
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
||||
describe("hyperframes skills", () => {
|
||||
beforeEach(() => {
|
||||
state.calls = [];
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("sets GIT_CLONE_PROTECTION_ACTIVE=0 on the spawned skills CLI child (GH #316)", async () => {
|
||||
const { default: skillsCmd } = await import("./skills.js");
|
||||
await skillsCmd.run?.({ args: {}, rawArgs: [], cmd: skillsCmd } as never);
|
||||
|
||||
const first = state.calls[0];
|
||||
expect(first).toBeDefined();
|
||||
expect(first!.command).toBe("npx");
|
||||
expect(first!.args).toContain("skills");
|
||||
expect(first!.args).toContain("add");
|
||||
expect(first!.env?.GIT_CLONE_PROTECTION_ACTIVE).toBe("0");
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,14 @@ function runSkillsAdd(repo: string): Promise<void> {
|
||||
const child = spawn("npx", ["skills", "add", repo, "--all"], {
|
||||
stdio: "inherit",
|
||||
timeout: 120_000,
|
||||
// GH #316 — the upstream `skills` CLI shells out to `git clone`.
|
||||
// When Git's clone-hook protection is active (shipped on by
|
||||
// default in 2.45.1, reverted in 2.45.2, still present on many
|
||||
// corporate and CI setups), any globally-registered
|
||||
// `git lfs install` post-checkout hook aborts the clone. The
|
||||
// `repo` reaching this function is hardcoded in SOURCES below
|
||||
// — no user input reaches the spawn — so opting out here is safe.
|
||||
env: { ...process.env, GIT_CLONE_PROTECTION_ACTIVE: "0" },
|
||||
});
|
||||
child.on("close", (code, signal) => {
|
||||
if (code === 0) resolve();
|
||||
|
||||
Reference in New Issue
Block a user