diff --git a/src/nemoclaw.ts b/src/nemoclaw.ts index c9ea19ddcf..66403813ed 100644 --- a/src/nemoclaw.ts +++ b/src/nemoclaw.ts @@ -1818,9 +1818,9 @@ function buildSandboxLogsArgs(sandboxName: string, follow: boolean): string[] { * for a single custom preset YAML, and `--from-dir ` for every * `.yaml`/`.yml` file in a directory. `--dry-run` previews without applying, * `--yes`/`-y`/`--force` (or `NEMOCLAW_NON_INTERACTIVE=1`) skips the - * confirmation prompt. `--from-dir` applies files in lexicographic order - * and aborts at the first failure (already-applied presets are not rolled - * back). + * confirmation prompt. `--from-dir` applies non-hidden files in lexicographic + * order and aborts at the first failure (already-applied presets are not + * rolled back). */ async function sandboxPolicyAdd(sandboxName: string, args: string[] = []): Promise { const dryRun = args.includes("--dry-run"); @@ -1862,7 +1862,10 @@ async function sandboxPolicyAdd(sandboxName: string, args: string[] = []): Promi } const files = fs .readdirSync(absDir, { withFileTypes: true }) - .filter((ent: { name: string; isFile(): boolean }) => ent.isFile() && /\.ya?ml$/i.test(ent.name)) + .filter( + (ent: { name: string; isFile(): boolean }) => + ent.isFile() && !ent.name.startsWith(".") && /\.ya?ml$/i.test(ent.name), + ) .map((ent: { name: string }) => path.join(absDir, ent.name)) .sort(); if (files.length === 0) { diff --git a/test/policies.test.ts b/test/policies.test.ts index 8b90bcc419..e614d10dce 100644 --- a/test/policies.test.ts +++ b/test/policies.test.ts @@ -1497,5 +1497,23 @@ setImmediate(() => { expect(loads.length).toBe(1); expect(loads[0]).toMatch(/real\.yaml$/); }); + + it("--from-dir skips hidden dotfile yaml presets", () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-from-dir-dotfile-")); + fs.writeFileSync( + path.join(dir, ".bad.yaml"), + "preset:\n name: bad\nnetwork_policies: {}\n", + ); + fs.writeFileSync( + path.join(dir, "real.yaml"), + "preset:\n name: real\nnetwork_policies: {}\n", + ); + const result = runPolicyAddExternal(["--from-dir", dir, "--yes"]); + expect(result.status).toBe(0); + const calls = JSON.parse(result.stdout.split("__CALLS__")[1].trim()) as PolicyCall[]; + const loads = calls.filter((c) => c.type === "load").map((c) => c.path); + expect(loads.length).toBe(1); + expect(loads[0]).toMatch(/real\.yaml$/); + }); }); });