Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/actions/ci-static-checks/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,11 @@ runs:
shell: bash
run: npm run validate:configs

# TypeScript checks and version sync run in the build-typecheck job.
- name: Run static hook checks
shell: bash
run: |
npx prek run --all-files --stage pre-push \
--skip tsc-plugin \
--skip tsc-js \
--skip tsc-cli \
--skip version-tag-sync \
npx prek run --all-files --stage pre-commit \
--skip test-cli \
--skip test-plugin \
--skip source-shape-test-budget \
Expand Down
11 changes: 11 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

exclude: ^(nemoclaw/dist/|nemoclaw/node_modules/|docs/_build/|\.venv/|uv\.lock$)

# Stage-less hooks run only while creating a commit. Hooks for later Git
# lifecycle stages declare their own `stages` explicitly below.
default_stages:
- pre-commit

# Which git hook shims `prek install` writes (separate from each hook's `stages:`).
# https://prek.j178.dev/configuration/#default_install_hook_types
default_install_hook_types:
Expand All @@ -42,9 +47,11 @@ repos:
hooks:
- id: trailing-whitespace
exclude: ^skills/[^/]+/skill\.oms\.sig$
stages: [pre-commit]
priority: 0
- id: end-of-file-fixer
exclude: ^skills/[^/]+/skill\.oms\.sig$
stages: [pre-commit]
priority: 0
- id: mixed-line-ending
args: ["--fix=lf"]
Expand Down Expand Up @@ -98,6 +105,7 @@ repos:
- "2"
- -ci
- -bn
stages: [pre-commit]
priority: 5

- repo: local
Expand Down Expand Up @@ -129,6 +137,7 @@ repos:
priority: 10
- id: check-added-large-files
args: ["--maxkb=2000"]
stages: [pre-commit]
priority: 10
- id: check-case-conflict
priority: 10
Expand All @@ -141,8 +150,10 @@ repos:
- id: detect-private-key
priority: 10
- id: check-executables-have-shebangs
stages: [pre-commit]
priority: 10
- id: check-shebang-scripts-are-executable
stages: [pre-commit]
priority: 10

- repo: local
Expand Down
38 changes: 32 additions & 6 deletions test/pr-workflow-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ type CodebaseGrowthGuardrailsWorkflow = {
jobs: Record<string, WorkflowJob>;
};

type PrekConfig = {
default_stages?: string[];
repos: Array<{
hooks?: Array<{ id: string; stages?: string[] }>;
}>;
};

const sharedActionPaths = {
staticChecks: "./.github/actions/ci-static-checks",
buildTypecheck: "./.github/actions/ci-build-typecheck",
Expand Down Expand Up @@ -129,6 +136,7 @@ function codeFilterMatchesChangedPaths(workflow: CiWorkflow, paths: string[]): b
describe("pull request and main workflow contracts", () => {
const prWorkflow = readYaml<CiWorkflow>(".github/workflows/pr.yaml");
const mainWorkflow = readYaml<CiWorkflow>(".github/workflows/main.yaml");
const prekConfig = readYaml<PrekConfig>(".pre-commit-config.yaml");
const sharedActions = {
staticChecks: readYaml<CompositeAction>(".github/actions/ci-static-checks/action.yaml"),
buildTypecheck: readYaml<CompositeAction>(".github/actions/ci-build-typecheck/action.yaml"),
Expand Down Expand Up @@ -166,6 +174,28 @@ describe("pull request and main workflow contracts", () => {
).toBe(true);
});

it("keeps ordinary hooks in pre-commit and heavyweight push hooks explicit", () => {
const hooks = prekConfig.repos.flatMap((repo) => repo.hooks ?? []);
const hook = (id: string) => hooks.find((candidate) => candidate.id === id);

expect(prekConfig.default_stages).toEqual(["pre-commit"]);
expect(hook("test-cli")?.stages).toBeUndefined();
expect(hook("test-plugin")?.stages).toBeUndefined();
for (const id of [
"trailing-whitespace",
"end-of-file-fixer",
"shfmt",
"check-added-large-files",
"check-executables-have-shebangs",
"check-shebang-scripts-are-executable",
]) {
expect(hook(id)?.stages, id).toEqual(["pre-commit"]);
}
for (const id of ["tsc-plugin", "tsc-js", "tsc-cli", "version-tag-sync"]) {
expect(hook(id)?.stages, id).toEqual(["pre-push"]);
}
});

it("reuses the same shared CI actions in PR and main workflows", () => {
for (const [jobName, stepName, trustedActionPath, mainActionPath] of [
[
Expand Down Expand Up @@ -353,7 +383,7 @@ describe("pull request and main workflow contracts", () => {
const staticRuns = stepRuns(sharedActions.staticChecks);
const staticRunsJoined = staticRuns.join("\n");
const staticPrekRun = staticRuns.find((run) =>
run.includes("npx prek run --all-files --stage pre-push"),
run.includes("npx prek run --all-files --stage pre-commit"),
);
const buildRuns = stepRuns(sharedActions.buildTypecheck);
const cliShardRuns = stepRuns(sharedActions.cliCoverageShard).join("\n");
Expand All @@ -363,12 +393,8 @@ describe("pull request and main workflow contracts", () => {

expect(staticRuns).toContain("npm install --ignore-scripts");
expect(staticRuns).toContain("npm run validate:configs");
expect(staticPrekRun).toContain("npx prek run --all-files --stage pre-push");
expect(staticPrekRun).toContain("npx prek run --all-files --stage pre-commit");
for (const skippedHook of [
"tsc-plugin",
"tsc-js",
"tsc-cli",
"version-tag-sync",
"test-cli",
"test-plugin",
"source-shape-test-budget",
Expand Down
11 changes: 10 additions & 1 deletion test/sandbox-rlimit-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ function runLoggedDockerShell(command: string, tmp: string) {
}

function copyRlimitFixture(rlimitLib: string): void {
copyRlimitFixtureWithNprocLimit(rlimitLib, process.platform === "darwin" ? 4096 : 512);
// TEST-ONLY OVERRIDE: production remains 512 in scripts/lib/sandbox-rlimits.sh.
// RLIMIT_NPROC is shared by the real user, so that default can starve this
// test's own shell when Vitest runs many workers concurrently.
copyRlimitFixtureWithNprocLimit(rlimitLib, 4096);
}

function copyRlimitFixtureWithNprocLimit(rlimitLib: string, limit: number): void {
Expand Down Expand Up @@ -290,6 +293,12 @@ function expectUnsupportedNprocDoesNotMaskPosixShNoFile(rlimitLib: string): void
}

describe("sandbox rlimit system hooks (#2173)", () => {
it("keeps the production nproc default at 512", () => {
expect(fs.readFileSync(SANDBOX_RLIMITS, "utf-8")).toMatch(
/^NEMOCLAW_SANDBOX_NPROC_LIMIT=512$/m,
);
});

it("rlimit helper enforces supported nofile limits under POSIX sh", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-posix-sh-rlimit-"));
const rlimitLib = path.join(tmp, "sandbox-rlimits.sh");
Expand Down
Loading