From 0c4bbcfcf3e2b61a4743bda96b8148b430f6c811 Mon Sep 17 00:00:00 2001 From: dyoshikawa Date: Fri, 31 Jul 2026 18:33:49 -0700 Subject: [PATCH 1/2] fix(generate): fail the run when the permissions feature errors With a malformed .vibe/config.toml, --features mcp exited 1 while --features permissions swallowed the same parse failure with a warning and reported 'All files are up to date' - a silent false-success where the user's permission changes were not applied and nothing signaled it. The permissions loop now logs at error level and rethrows, matching the MCP feature's behavior on the same file. Regression coverage: an e2e case asserts both features exit 1 on the same malformed fixture, and the generate unit test that pinned the old swallow-and-continue behavior now pins the fail-the-run behavior. Closes #2486 Co-Authored-By: Claude Fable 5 --- src/e2e/e2e-permissions.spec.ts | 23 +++++++++++++++++++++++ src/lib/generate.test.ts | 12 ++++++++---- src/lib/generate.ts | 8 ++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/e2e/e2e-permissions.spec.ts b/src/e2e/e2e-permissions.spec.ts index e23694721..ab6a3f89b 100644 --- a/src/e2e/e2e-permissions.spec.ts +++ b/src/e2e/e2e-permissions.spec.ts @@ -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(); diff --git a/src/lib/generate.test.ts b/src/lib/generate.test.ts index cf7e4960f..3e1ce712e 100644 --- a/src/lib/generate.test.ts +++ b/src/lib/generate.test.ts @@ -662,15 +662,19 @@ 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")); }); }); diff --git a/src/lib/generate.ts b/src/lib/generate.ts index 1ba69710c..1b6307e1c 100644 --- a/src/lib/generate.ts +++ b/src/lib/generate.ts @@ -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; } } } From f4fe77fcb8aa96757dc59cc7c72c59bfe18c3951 Mon Sep 17 00:00:00 2001 From: dyoshikawa Date: Fri, 31 Jul 2026 18:35:07 -0700 Subject: [PATCH 2/2] style: format generate test Co-Authored-By: Claude Fable 5 --- src/lib/generate.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/generate.test.ts b/src/lib/generate.test.ts index 3e1ce712e..138ca36ef 100644 --- a/src/lib/generate.test.ts +++ b/src/lib/generate.test.ts @@ -671,9 +671,7 @@ describe("generate", () => { throw new Error("Test error"); }); - await expect(generate({ logger, config: mockConfig as never })).rejects.toThrow( - "Test error", - ); + await expect(generate({ logger, config: mockConfig as never })).rejects.toThrow("Test error"); expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Test error")); }); });