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]

Reply via email to