This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new dc2fa1aafb00 CAMEL-26527: camel debug --background bypasses TTY check
for non-interactive sessions (#29153)
dc2fa1aafb00 is described below
commit dc2fa1aafb004d78c5c5dab969a23cebd291ccf5
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Nov 9 11:09:15 2026 +0100
CAMEL-26527: camel debug --background bypasses TTY check for
non-interactive sessions (#29153)
* CAMEL-26527: camel debug --background bypasses TTY check for
non-interactive sessions
* CAMEL-26527: address review — reject --background for SB/Quarkus paths,
use runBackgroundProcess in plain mode, reject --background + --remote-attach
* CAMEL-26527: address review — kind string, happy-path test, doc update
* CAMEL-26527: address review — fix doc to match runBackgroundProcess
semantics, add kind assertion to test
---
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 2 +-
.../modules/ROOT/pages/camel-jbang-debugging.adoc | 23 +++++++++++
.../modules/ROOT/pages/camel-jbang-exit-codes.adoc | 2 +-
.../camel/dsl/jbang/core/commands/Debug.java | 41 ++++++++++++++++++-
.../core/commands/CommandInputErrorsTest.java | 46 ++++++++++++++++++++++
.../camel/dsl/jbang/it/CamelDebugITCase.java | 2 +-
6 files changed, 111 insertions(+), 5 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 327a8398dd8f..14c0c98d828d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2503,7 +2503,7 @@ Without an interactive terminal, such as in a script, a
CI job (the `CI` environ
`--open-url` (the old name still works).
* `camel debug`, `camel nano`, `camel shell`, `camel edit`, `camel tui`
(except with `--record`), `camel ask` without a
question, `camel run --prompt` and `camel get history --it` exit with `2`
and `requires an interactive terminal` on
- stderr. `camel debug` used to wait at 100% CPU.
+ stderr. `camel debug` used to wait at 100% CPU. `camel debug --background`
does not require an interactive terminal.
`--follow` and `--open-url` without a value turn the option on. As boolean
options that are on by default, they turned
it off (use `--follow=false` to turn it off). In a terminal, the end of the
input (Ctrl+D) stops a command that watches
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
index 79dc91ec33b1..cda7c972c642 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
@@ -72,6 +72,29 @@ camel debug hello.yaml --breakpoint=18
camel debug hello.yaml --breakpoint=18,34
----
+=== Running the debugger in background mode
+
+Use `--background` to start the debug-enabled integration without an
interactive terminal:
+
+[source,bash]
+----
+camel debug hello.yaml --background
+----
+
+This is useful in CI or non-interactive sessions where no TTY is available.
+Unlike the interactive mode, `camel debug --background` does not require a
terminal:
+it launches the Camel integration with the route debugger enabled, writes its
output to
+`~/.camel/<pid>-run.log`, and waits up to `--background-wait` (60 seconds by
default) for
+the integration to start.
+It exits with `0` once the integration is running, with the integration's exit
code when it
+fails to start, and with `124` when it is still starting at the end of the
wait.
+
+NOTE: `--background` is not supported for Spring Boot or Quarkus projects.
+ `--background` and `--remote-attach` are mutually exclusive.
+
+Once the integration is running in the background with the route debugger
enabled, you can
+attach to it from an interactive terminal with `camel debug --remote-attach
<name>`.
+
=== Spring Boot route debugging
*Available since Camel 4.14*
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-exit-codes.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-exit-codes.adoc
index b3753abb23ec..83b9584a4daa 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-exit-codes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-exit-codes.adoc
@@ -143,7 +143,7 @@ so the commands do not wait for it:
* A command that watches (`--watch`) or follows (`camel log`, `camel trace`,
`camel cmd receive` and
`camel infra log`) shows its output once. With `--duration`, such as
`--duration=30s`, it keeps watching or following
for that long, and then exits with `0`. With `--follow`, it follows until it
is stopped.
-* A command that only works in a terminal, such as `camel debug`, `camel
shell` or `camel tui`, exits with `2`.
+* A command that only works in a terminal, such as `camel debug` (except with
`--background`), `camel shell` or `camel tui`, exits with `2`.
[source,bash]
----
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
index 6f788122dea0..6141251009d3 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
@@ -42,6 +42,7 @@ import
org.apache.camel.dsl.jbang.core.commands.action.MessageTableHelper;
import org.apache.camel.dsl.jbang.core.common.ActionClient;
import org.apache.camel.dsl.jbang.core.common.ActionClient.ActionResult;
import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
+import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper;
@@ -169,8 +170,13 @@ public class Debug extends Run {
@Override
public Integer doCall() throws Exception {
- // the debugger reads its commands from the terminal
- requireInteractiveTerminal("camel debug");
+ // the debugger reads its commands from the terminal; skip the check
in background mode
+ if (!background) {
+ requireInteractiveTerminal("camel debug");
+ }
+ if (background && remoteAttach) {
+ throw CliException.usage("--background and --remote-attach are
mutually exclusive");
+ }
if (!remoteAttach) {
checkFilesExist();
skipCitrusTests();
@@ -187,6 +193,11 @@ public class Debug extends Run {
return exit;
}
+ // in background mode, the integration runs as a detached child
process; return immediately
+ if (background) {
+ return 0;
+ }
+
// to shut down debug process if ctrl + c
if (stopOnExit) {
installHangupInterceptor();
@@ -330,6 +341,22 @@ public class Debug extends Run {
} while (!quit.get());
}
+ @Override
+ protected int runSpringBoot() throws Exception {
+ if (background) {
+ throw CliException.usage("Debug Camel Spring Boot with
--background is not supported");
+ }
+ return super.runSpringBoot();
+ }
+
+ @Override
+ protected int runQuarkus() throws Exception {
+ if (background) {
+ throw CliException.usage("Debug Camel Quarkus with --background is
not supported");
+ }
+ return super.runQuarkus();
+ }
+
@Override
protected int runDebug(KameletMain main) throws Exception {
File pom = new File("pom.xml");
@@ -337,8 +364,14 @@ public class Debug extends Run {
try (InputStream is = new FileInputStream(pom)) {
String text = IOHelper.loadText(is);
if (text.contains("camel-spring-boot-bom")) {
+ if (background) {
+ throw CliException.usage("Debug Camel Spring Boot with
--background is not supported");
+ }
return doRunDebugSpringBoot(main);
} else if (text.contains("org.apache.camel.quarkus")) {
+ if (background) {
+ throw CliException.usage("Debug Camel Quarkus with
--background is not supported");
+ }
return doRunDebugQuarkus(main);
}
}
@@ -579,6 +612,10 @@ public class Debug extends Run {
ProcessBuilder pb = new ProcessBuilder();
pb.command(cmds);
+ if (background) {
+ return runBackgroundProcess(pb, "Camel Debug");
+ }
+
Process p = pb.start();
this.spawnOutput = p.getInputStream();
this.spawnError = p.getErrorStream();
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CommandInputErrorsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CommandInputErrorsTest.java
index 431766650aa0..2b25452246a5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CommandInputErrorsTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CommandInputErrorsTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.jbang.core.commands;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.camel.dsl.jbang.core.commands.action.EvalExpressionCommand;
import org.apache.camel.dsl.jbang.core.commands.action.TransformMessageAction;
@@ -154,6 +155,51 @@ class CommandInputErrorsTest {
.extracting(e -> ((CliException)
e).getExitCode()).isEqualTo(CliExitCode.USAGE);
}
+ @Test
+ void debugSpringBootInTheBackground() throws Exception {
+ // Spring Boot registers stopOnExit hooks that kill the child when
this JVM exits; --background exits
+ // immediately and would stop the integration before it starts
+ Path pom = Files.writeString(dir.resolve("pom.xml"),
+ "<project><dependencyManagement><dependencies>"
+ +
"<dependency><groupId>org.apache.camel.springboot</groupId>"
+ +
"<artifactId>camel-spring-boot-bom</artifactId><version>4.9.0</version>"
+ +
"<type>pom</type><scope>import</scope></dependency>"
+ +
"</dependencies></dependencyManagement></project>");
+
+ assertThatThrownBy(() -> call(new Debug(main), "--background",
pom.toString()))
+ .isInstanceOf(CliException.class)
+ .hasMessage("Debug Camel Spring Boot with --background is not
supported")
+ .extracting(e -> ((CliException)
e).getExitCode()).isEqualTo(CliExitCode.USAGE);
+ }
+
+ @Test
+ void debugInTheBackgroundSkipsTtyCheck() throws Exception {
+ // --background makes camel debug run without an interactive terminal:
+ // the TTY check that guards the interactive path must be bypassed
+ Path file = Files.writeString(dir.resolve("orders.yaml"), "- from:\n
uri: timer:x\n");
+ assumeFalse(EnvironmentHelper.isInteractiveTerminal(), "needs a
non-interactive terminal");
+ // Override runBackgroundProcess so the test does not actually spawn a
child JVM,
+ // and record the kind string to verify the background branch was taken
+ AtomicReference<String> capturedKind = new AtomicReference<>();
+ Debug debug = new Debug(main) {
+ @Override
+ protected int runBackgroundProcess(ProcessBuilder pb, String kind)
{
+ capturedKind.set(kind);
+ return 0;
+ }
+ };
+ assertThat(call(debug, "--background", file.toString())).isZero();
+ assertThat(capturedKind.get()).isEqualTo("Camel Debug");
+ }
+
+ @Test
+ void debugBackgroundAndRemoteAttachAreMutuallyExclusive() {
+ assertThatThrownBy(() -> call(new Debug(main), "--background",
"--remote-attach"))
+ .isInstanceOf(CliException.class)
+ .hasMessage("--background and --remote-attach are mutually
exclusive")
+ .extracting(e -> ((CliException)
e).getExitCode()).isEqualTo(CliExitCode.USAGE);
+ }
+
@ParameterizedTest
@ValueSource(strings = { "missing.yaml", "missing.xml", "missing.java",
"file:missing.camel.yaml" })
void runAFileThatDoesNotExist(String file) {
diff --git
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
index 910eded0830a..9b186fe2d924 100644
---
a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
+++
b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/CamelDebugITCase.java
@@ -29,7 +29,7 @@ public class CamelDebugITCase extends JBangTestSupport {
@Test
public void testDebug() throws IOException {
copyResourceInDataFolder(TestResources.ROUTE2);
- execNohup(String.format("debug %s/route2.yaml", mountPoint()));
+ executeBackground(String.format("debug %s/route2.yaml", mountPoint()));
checkLogContains("Debugger JMXConnector listening at:");
}
}