This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit 7b576f7edb00b0d088e032f8febdccb686ca92aa Author: Jinal Sarvaiya <[email protected]> AuthorDate: Mon Nov 9 12:24:30 2026 +0000 fix #99: remove stale Ship worker when project config validation or merge aborts Signed-off-by: Jinal Sarvaiya <[email protected]> --- .../camel/kit/generator/OpenCodeGenerator.java | 30 +++++++++- .../camel/kit/generator/OpenCodeGeneratorTest.java | 64 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java index 62e62f2b..7c41b2c0 100644 --- a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java +++ b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java @@ -38,11 +38,39 @@ public class OpenCodeGenerator extends DefaultGenerator { @Override public void preflight(InitContext ctx) throws Exception { - new OpenCodeConfigMerger().validateExisting(ctx.projectDir().resolve(ctx.agent().mcpConfigPath())); + try { + new OpenCodeConfigMerger().validateExisting(ctx.projectDir().resolve(ctx.agent().mcpConfigPath())); + } catch (Exception e) { + // OpenCode substitutes {env:}/{file:} before parsing, so a project config that is invalid as raw + // JSON may still be valid for the host. Initialization aborts here, so remove the installed worker: + // its denial list can no longer be shown to match the effective MCP server set. Other validation + // failures (e.g. duplicate keys) keep the "fail before any project writes" contract. + if (projectConfigMayContainSubstitution(ctx)) { + Files.deleteIfExists(ctx.projectDir().resolve(".opencode/agents/camel-ship-worker.md")); + } + throw e; + } + } + + private static boolean projectConfigMayContainSubstitution(InitContext ctx) { + try { + for (Path configFile : OpenCodeProjectConfig.existingFiles(ctx.projectDir())) { + if (SUBSTITUTION_PATTERN.matcher(Files.readString(configFile)).find()) { + return true; + } + } + return false; + } catch (IOException e) { + return true; // unreadable: fail closed + } } @Override public void generate(InitContext ctx) throws Exception { + // Invalidate the worker before project-config merging can abort; it is regenerated below only if the + // effective MCP inventory is bounded. + Files.deleteIfExists(ctx.projectDir().resolve(".opencode/agents/camel-ship-worker.md")); + // Run default generation (commands, skills, MCP config) super.generate(ctx); diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java index ba7a6c05..9b57439f 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/OpenCodeGeneratorTest.java @@ -1572,6 +1572,70 @@ class OpenCodeGeneratorTest { "Ship worker must not be generated when a config source cannot be parsed"); } + @Test + void shipWorkerRemovedOnPreflightWhenRootProjectConfigHasStructuralSubstitution() throws Exception { + InitContext ctx = createContext(); + new OpenCodeGenerator().generate(ctx); + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertTrue(Files.exists(workerFile), "precondition: worker exists after initial generation"); + + Files.writeString(tempDir.resolve("opencode.json"), "{\"mcp\":{env:MCP_SERVERS}}"); + + assertThrows(Exception.class, () -> new OpenCodeGenerator().preflight(ctx)); + + assertFalse(Files.exists(workerFile), + "Worker must be removed before root project config validation aborts"); + } + + @Test + void shipWorkerRemovedOnReinitWhenRootProjectConfigHasStructuralSubstitution() throws Exception { + InitContext ctx = createContext(); + new OpenCodeGenerator().generate(ctx); + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertTrue(Files.exists(workerFile), "precondition: worker exists after initial generation"); + + Files.writeString(tempDir.resolve("opencode.json"), "{\"mcp\":{env:MCP_SERVERS}}"); + + assertThrows(Exception.class, () -> new OpenCodeGenerator().generate(ctx)); + + assertFalse(Files.exists(workerFile), + "Worker must be removed before root project config merging aborts"); + } + + @Test + void shipWorkerRemovedOnPreflightWhenNestedProjectConfigHasStructuralSubstitution() throws Exception { + InitContext ctx = createContext(); + new OpenCodeGenerator().generate(ctx); + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertTrue(Files.exists(workerFile), "precondition: worker exists after initial generation"); + + Path nested = tempDir.resolve(".opencode/opencode.json"); + Files.createDirectories(nested.getParent()); + Files.writeString(nested, "{\"mcp\":{env:MCP_SERVERS}}"); + + assertThrows(Exception.class, () -> new OpenCodeGenerator().preflight(ctx)); + + assertFalse(Files.exists(workerFile), + "Worker must be removed before nested project config validation aborts"); + } + + @Test + void shipWorkerRemovedOnReinitWhenNestedProjectConfigHasStructuralSubstitution() throws Exception { + InitContext ctx = createContext(); + new OpenCodeGenerator().generate(ctx); + Path workerFile = tempDir.resolve(".opencode/agents/camel-ship-worker.md"); + assertTrue(Files.exists(workerFile), "precondition: worker exists after initial generation"); + + Path nested = tempDir.resolve(".opencode/opencode.json"); + Files.createDirectories(nested.getParent()); + Files.writeString(nested, "{\"mcp\":{env:MCP_SERVERS}}"); + + assertThrows(Exception.class, () -> new OpenCodeGenerator().generate(ctx)); + + assertFalse(Files.exists(workerFile), + "Worker must be removed before nested project config merging aborts"); + } + @Test void shipWorkerDeniesServersFromWindowsProgramDataManagedConfigThroughGeneration() throws Exception { Path programData = tempDir.resolve("CompanyData");
