karatelabs/karate · error · RuntimeException
failed to start process:
Error message
failed to start process:
What it means
ProcessHandle.start() wraps any exception thrown while launching the subprocess (ProcessBuilder.start() failures, IO errors setting up streams, executor creation) into 'failed to start process: <message>' with the original exception as cause. The library throws so callers get one clear error when the OS process could not be launched.
Solutions
- Read the cause: 'Cannot run program ... No such file or directory' means the executable path is wrong or not installed
- Verify the command exists on PATH (or use an absolute path) and is executable (chmod +x)
- Check config.workingDir() points to an existing directory
- Test the exact args array manually in a shell to reproduce the launch failure
Example fix
// before
ProcessConfig config = cfg.command("mvn test").workingDir(Path.of("./nonexistent"));
// after
ProcessConfig config = cfg.command("mvn", "test").workingDir(Path.of("/absolute/project/dir")); Defensive patterns
Strategy: try-catch
Validate before calling
Path cmd = Path.of(command.split(" ")[0]);
if (!java.nio.file.Files.isExecutable(cmd) && findOnPath(command.split(" ")[0]) == null) {
throw new IllegalStateException("executable not found: " + command);
}
if (workingDir != null && !java.nio.file.Files.isDirectory(workingDir)) {
throw new IllegalStateException("workingDir is not a directory: " + workingDir);
} Try / catch
try {
handle.start();
} catch (RuntimeException e) {
if (e.getMessage().startsWith("failed to start process:")) {
throw new IllegalStateException("check command path/permissions/workingDir", e.getCause());
}
throw e;
} Prevention
- Use absolute executable paths in CI images
- chmod +x or verify executability before forking
- Validate workingDir exists and is a directory
- Smoke-test the args array in a plain shell first
When it happens
Trigger: start()/fork()/exec with a command that does not exist (IOException: Cannot run program), a workingDir that does not exist or is not a directory, invalid args/env configuration, or OS-level permission denial when exec'ing the binary.
Common situations: Wrong path to an executable in fork config, workingDir typo, binary without +x permission, command not installed on CI image, or quoting/argument mistakes in config.args().
Related errors
- timeout waiting for stream readers - this should not happen
- Failed to read bytes from
- exec() needs at least one argument
- ext ' ': resource vanished after validation
- ext ' ': failed reading
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/e95ea84476ad9196.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/process/ProcessHandle.java:163
}
try {
java.lang.ProcessBuilder pb = new java.lang.ProcessBuilder(config.args());
if (config.workingDir() != null) {
pb.directory(config.workingDir().toFile());
}
if (!config.env().isEmpty()) {
pb.environment().putAll(config.env());
}
pb.redirectErrorStream(config.redirectErrorStream());
logger.debug("starting process: {}", config.args());
this.process = pb.start();
this.executor = Executors.newVirtualThreadPerTaskExecutor();
LIVE_HANDLES.add(this);
startStreamReaders();
startExitWaiter();
return this;
} catch (Exception e) {
throw new RuntimeException("failed to start process: " + e.getMessage(), e);
}
}
/**
* Add a stdout listener. Can be called before or after start().
* When redirectErrorStream is true (default), receives both stdout and stderr.
*/
public ProcessHandle onStdOut(Consumer<String> listener) {
stdOutListeners.add(listener);
return this;
}
/**
* Add a stderr listener. Can be called before or after start().
* Only receives lines when redirectErrorStream is false.
*/
public ProcessHandle onStdErr(Consumer<String> listener) {
stdErrListeners.add(listener);View on GitHub (pinned to a22eb90246)