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

  1. Read the cause: 'Cannot run program ... No such file or directory' means the executable path is wrong or not installed
  2. Verify the command exists on PATH (or use an absolute path) and is executable (chmod +x)
  3. Check config.workingDir() points to an existing directory
  4. 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

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


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)