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
23 changes: 23 additions & 0 deletions src/e2e/e2e-permissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,29 @@ describe("E2E: permissions", () => {
},
);

it.each([{ features: "permissions" }, { features: "mcp" }])(
"should fail $features generation on a malformed .vibe/config.toml instead of reporting success",
async ({ features }) => {
const testDir = getTestDir();

await writeFileContent(join(testDir, ".vibe", "config.toml"), "mcp: [unclosed");
await writeFileContent(
join(testDir, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH),
JSON.stringify({ permission: { bash: { "*": "allow" } } }),
);
await writeFileContent(
join(testDir, ".rulesync", "mcp.json"),
JSON.stringify({ mcpServers: { srv: { command: "node" } } }),
);

// Both features read the same shared file and must agree on what a
// malformed file means: fail the run, never a silent "up to date".
await expect(runGenerate({ target: "vibe", features })).rejects.toMatchObject({
code: 1,
});
},
);

it("should generate rovodev permissions into the repo-committed .rovodev/config.yml", async () => {
const testDir = getTestDir();

Expand Down
10 changes: 6 additions & 4 deletions src/lib/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,15 +662,17 @@ describe("generate", () => {
);
});

it("should handle errors gracefully and continue", async () => {
it("should fail the run on a permissions error instead of reporting success", async () => {
// A malformed shared config used to be swallowed with a warning while
// the run exited 0 "up to date" — the MCP feature failed on the same
// file. Both features must agree that a broken file fails the run.
mockConfig.getFeatures.mockReturnValue(["permissions"]);
vi.mocked(PermissionsProcessor).mockImplementation(function () {
throw new Error("Test error");
});

const result = await generate({ logger, config: mockConfig as never });

expect(result.permissionsCount).toBe(0);
await expect(generate({ logger, config: mockConfig as never })).rejects.toThrow("Test error");
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Test error"));
});
});

Expand Down
8 changes: 6 additions & 2 deletions src/lib/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1134,10 +1134,14 @@ async function generatePermissionsCore(params: {
allPaths.push(...result.paths);
if (result.hasDiff) hasDiff = true;
} catch (error) {
logger.warn(
// A malformed shared config (e.g. .vibe/config.toml) must fail the run
// the same way the MCP feature does — swallowing it reported
// "All files are up to date" while the user's permission changes were
// silently not applied.
logger.error(
`Failed to generate ${toolTarget} permissions files for ${outputRoot}: ${formatError(error)}`,
);
continue;
throw error;
}
}
}
Expand Down
Loading