Croway opened a new pull request, #28678: URL: https://github.com/apache/camel/pull/28678
_Claude Code on behalf of Croway_ [CAMEL-26396](https://issues.apache.org/jira/browse/CAMEL-26396) ## Problem A local `camel_eval_expression` (no integration selected) starts a scratch `DefaultCamelContext` inside the MCP server. camel-cli-connector is on the server's classpath, so every scratch context started a CLI connector, which wrote `~/.camel/<server pid>-status.json` while it ran. `FileCliConnectorTransport.doStop` cancelled its status task with `cancel(false)` and deleted the files right away, so a status write already in flight recreated the file after the delete: the status of a context that had already shut down (`phase 4`, `state Starting`, named `camel-14` and the like) stayed behind. From then on the tool layer took the server for an integration, because `RuntimeHelper` matches any live pid with a status file, the current one included: - `camel_runtime_processes` listed the server itself; - `camel_eval_expression` without a name selected "the only running integration", the server, and waited 10 s for an answer nobody gives ("No answer from the integration with pid 6 to the action eval within 10 seconds"); this is sticky, as no scratch context runs any more that could delete the file; - with a real integration next to it, calls without a name were refused as ambiguous; - `camel_control kill` of the server's pid ended as JSON-RPC `-32603` (`IllegalStateException: destroy of current process not allowed` from `ProcessHandle.destroyForcibly`). Reproduced in a Linux container (`--cpus=1` and `--cpus=0.5`, 3 of 3 runs): after 11-26 local evaluations the status file stays behind. On macOS, while a slow local evaluation runs, the server lists itself as `camel-3`, and a concurrent evaluation without a name is answered by the scratch context's connector and labelled as evaluated in the running integration. ## Fix | Where | Change | |---|---| | `ScratchContextHelper` (new, camel-jbang-core `common`) | `withoutCliConnector(context)` binds a disabled `CliConnectorFactory` in the context's registry (picked up when the context builds) and sets it as the context plugin (for a context built already); `disabledCliConnectorFactory()` for a Camel Main. | | `ExpressionEvaluator.evaluateWith` | The scratch context starts without the CLI connector. | | `VersionList.downloadReleases` | The `KameletMain` it starts only to download (also from the MCP tool `camel_version_list`, inside the server) runs without the CLI connector. | | `RuntimeHelper.discoverProcesses` / `findProcess` | Never return this process (the CLI, camel ask, the TUI or the MCP server running the tools), also not by its pid. New `isThisProcess(nameOrPid)`. `ProcessFinder` is unchanged: CLI commands can still match the current process by pid. | | `ToolContext.selectProcess(String)`, `select_process` | A name that is this process's pid is refused with a `ToolExecutionException`: "pid N is the process that runs these tools (the MCP server, camel ask or the TUI), not a Camel integration; list_processes shows the integrations". | | `IntegrationLauncher.control` | Refuses this process's pid for every action with a `ToolExecutionException`, so no action on it ends as `-32603` (and `restart` cannot start a copy of the server). | | MCP `RuntimeService.findSingleProcess` | The same for the `camel_runtime_*` tools: "pid N is this MCP server (the process that runs the tools), not a Camel integration". | | `FileCliConnectorTransport.doStop` (camel-cli-connector) | After cancelling the status task, shuts the poll executor down and waits for the poll in flight (at most 5 s) before it deletes the files, so a stopped context cannot leave a status file behind. Not when the stop runs on the poll thread itself. The daemon thread (CAMEL-25230) and the "stop polling first" order are kept. | Audited and left unchanged, as these contexts are never initialised (the CLI connector starts in `AbstractCamelContext.doInit`): `RouteDslConverter.read`, `TransformJavaRoutes.dump`, `SourceHelper` (classpath resolver) and `Init` (GitHub / Gist resolvers). Checked with a throwaway test: converting YAML to XML and Java, Java to YAML, and dumping the structure of a Java route create nothing under `user.home`. The TUI already skips its own pid in its process list (`TuiHelper.findPids`) and gives the shared tools only the integration the user selected. camel ask only targets processes it discovers. Neither needs to select its own JVM. The one case is an integration that camel ask's `cli_exec` runs in its own JVM (`camel run --max-seconds=N`): the call waits for that run (and interrupts it after 60 s), so no other tool call inspects it meanwhile; a run that ignores the interrupt and goes on can no longer be selected by its pid. ## Testing New tests, each failing on origin/main (with a no-op `ScratchContextHelper` for the class that is new): | Test | On origin/main | |---|---| | `FileCliConnectorTransportStopTest` (camel-cli-connector): the status provider blocks the first poll, the transport stops meanwhile, then the poll goes on; the status file must not exist afterwards | fails: the poll's write recreates `<pid>-status.json` after the stop deleted it | | `ThisProcessIsNoIntegrationTest` (camel-jbang-core), with a status file of the test JVM's own pid in a temp camel dir | 5 of 5 fail: `discoverProcesses()` returns the JVM itself; `selectProcess("<own pid>")` selects it; `camel_eval_expression` without a name ends with "No answer from the integration with pid N to the action eval within 10 seconds"; a local evaluation creates `~/.camel` (the scratch context's connector); `control(kill)` throws `IllegalStateException: destroy of current process not allowed` | | `ScratchContextHelperTest`: a context built or not, and a `KameletMain`, start without a `CliConnector` and write nothing under `user.home` | 3 of 4 fail with the helper as a no-op (the fourth checks that the connector is on the classpath by default) | | `RuntimeServiceTest.theServerIsNeverAProcessOfItsOwn` (camel-jbang-mcp) | fails: the server is listed | `IntegrationLauncherGuardTest` faked the running integration with the test JVM's own pid; it now starts a small child JVM (`java Wait.java`, blocking on stdin) and writes the status file for that pid. Full module suites, in one reactor (`mvnd -P apache-snapshots -T 2 test -pl dsl/camel-cli-connector,dsl/camel-jbang/camel-jbang-core,dsl/camel-jbang/camel-jbang-mcp,dsl/camel-jbang/camel-jbang-plugin-tui`): | Module | Tests | |---|---| | camel-cli-connector | 76, 0 failures | | camel-jbang-core | 2522 + 244, 0 failures (2 skipped) | | camel-jbang-mcp | 506, 0 failures | | camel-jbang-plugin-tui | 2283, 0 failures (1 skipped; `MonitorStartupTest.browserOnlyServesUntilTheServerStops` flaked once with "Connection refused" and passed on the rerun) | End to end in a Linux container (`camel-mcp-repro:21`, `--cpus=1`): a stdio MCP client runs up to 200 local `camel_eval_expression` calls and checks for `~/.camel/<server pid>-status.json` after each one. | Runner jar | Result | |---|---| | origin/main | `evals run: 17, stale status file: True` | | this branch, run 1 | `evals run: 200, stale status file: False` | | this branch, run 2 | `evals run: 200, stale status file: False` | On this branch the server log shows 200 scratch contexts starting and no "Camel CLI connector enabled" line. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
