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");

Reply via email to