Share

Resolving TypeError: Cannot find function exec in Orchestrator 9.1.1

by Mayank Goyal · 27 Oct 2026

After upgrading to VCF Operations Orchestrator 9.1.1, JavaScript actions that run OS-level commands through Java classes stop working. If you have old code that calls java.lang.Runtime.exec() to run a shell command, it will now fail. In this post I’ll show what the error looks like, why it happens, and how to run the same commands, including curl API calls, from a Python action instead. Everything below was tested on my lab running Orchestrator 9.1.1.0 build 25643527.

Issue summary

Any JavaScript action or scriptable task that runs an OS command like this is affected:

var runtime = java.lang.Runtime.getRuntime();
var process = runtime.exec("uname -n");

On 9.1.1 this fails straight away. On my lab the error was:

TypeError: getRuntime is not a function, it is object.

Depending on your build, you may see this message instead:

TypeError: Cannot find function exec in object java.lang.Runtime@xxxx

Both mean the same thing: your script is no longer allowed to start OS processes.

Root cause

Running OS commands from JavaScript has been disabled in VCF Operations Orchestrator 9.1.1 for security reasons. The JavaScript engine no longer gives scripts access to java.lang.Runtime, and the same applies to java.lang.ProcessBuilder, the other common way of starting a process from Java.

The classes aren’t simply missing their methods. They can’t be reached at all, so the engine treats the name as an empty Java package placeholder. That’s why the error says getRuntime “is object”: the call fails before it ever gets to exec. You can see this for yourself with a quick check:

System.log("java.lang.Runtime = " + java.lang.Runtime);
System.log("typeof getRuntime = " + typeof java.lang.Runtime.getRuntime);

On 9.1.1 this logs:

java.lang.Runtime = [JavaPackage java.lang.Runtime]
typeof getRuntime = object

So there’s no workaround inside JavaScript. The command has to run somewhere else.

Solution: run the command from a Python action

Orchestrator’s polyglot runtimes aren’t affected. A Python action can start processes with the standard subprocess module, so the fix is to move the command into a Python action and call that action from your workflow.

To create it:

  1. Create a new action and set its Runtime to Python.
  2. Add one input named command of type string.
  3. Set the return type to string.
  4. Paste the script below and save.
import subprocess
import shlex

def handler(context, inputs):
    command = inputs["command"]
    print("Running: " + command)

    try:
        result = subprocess.run(
            shlex.split(command),
            capture_output=True,
            text=True,
            timeout=60
        )
    except FileNotFoundError:
        raise Exception("Command not found in the Python runtime: " + shlex.split(command)[0])

    print("Exit code: " + str(result.returncode))
    if result.stdout:
        print("STDOUT:\n" + result.stdout)
    if result.stderr:
        print("STDERR:\n" + result.stderr)

    if result.returncode != 0:
        raise Exception("Command failed with exit code " + str(result.returncode) + ": " + result.stderr.strip())

    return result.stdout.strip()

A few details in this script matter:

  • shlex.split splits the command the way a shell would, so quoted arguments stay together. A plain split(" ") breaks them apart.
  • text=True returns the output as normal text. Without it you get bytes, and str() of those looks like b'...\n'.
  • Exit codes are checked. If the command fails, the action fails with the command’s own error message, instead of quietly returning nothing.
  • A missing command gets a clear error. Without the FileNotFoundError handler you get a long Python traceback.
  • timeout=60 stops a hung command from blocking the workflow forever. Raise it if you run long jobs.

Testing it

Run the action with command set to uname -n:

INFO  Running: uname -n
INFO  Exit code: 0
INFO  STDOUT:
INFO  vco-app-0

Note that I used uname -n rather than hostname. The hostname command isn’t installed in the Python runtime, and trying it gives No such file or directory: 'hostname'.

Now try a command that fails, such as uname -h (not a valid option). The command still runs, and the script turns its error into a clear failure:

INFO  Running: uname -h
INFO  Exit code: 1
INFO  STDERR:
INFO  uname: invalid option -- 'h'
INFO  Try 'uname --help' for more information.
ERROR Exception: Command failed with exit code 1: uname: invalid option -- 'h'

Where does the command actually run?

The uname -n output, vco-app-0, is the name of Orchestrator’s own Kubernetes pod on the appliance. Your commands run inside that pod’s environment, not on the appliance’s host OS. Keep that in mind: a command like df or ip addr shows the pod’s view, not the virtual machine’s.

The runtime is a Photon OS image with a good set of tools. On my 9.1.1 lab it had over 400 commands, including:

  • Network and API: curl, wget, jq, openssl, ping, ip, ss, getent
  • Text and files: grep, sed, awk, cut, sort, tar, gzip, base64, sha256sum
  • Other runtimes: bash, pwsh, node, python3

The exact list can differ between builds. To see what’s available on yours, run this as a Python action with no inputs:

import os

def handler(context, inputs):
    found = []
    for d in ["/bin", "/usr/bin", "/usr/local/bin", "/sbin", "/usr/sbin"]:
        if os.path.isdir(d):
            names = sorted(os.listdir(d))
            print(d + " (" + str(len(names)) + "): " + ", ".join(names))
            found.append(d + ": " + str(len(names)) + " commands")
    return "\n".join(found)

Running curl with the same approach

A common reason for running OS commands from Orchestrator is calling an API with curl. The same Python action handles this with no code changes. A simple call works directly:

curl -sk https://auto.vmw.lab/vco/api/about

The action doesn’t use a shell, though, so pipes (|), && and redirects won’t work on their own. To use them, wrap the whole command in bash -c "...". Because the script uses shlex.split, everything inside the quotes reaches bash as one argument.

Here’s a real example from my lab. It calls the Orchestrator API with curl and parses the JSON with jq, all in one command:

bash -c "curl -sk https://auto.vmw.lab/vco/api/about | jq -r '\"Version: \" + .version + \" | Build: \" + .\"build-number\"'"

Output:

INFO  Running: bash -c "curl -sk https://auto.vmw.lab/vco/api/about | jq -r ..."
INFO  Exit code: 0
INFO  STDOUT:
INFO  Version: 9.1.1.0.25643527 | Build: 25643527

A couple more you can try with the same action:

# Check when an endpoint's TLS certificate expires
bash -c "echo | openssl s_client -connect auto.vmw.lab:443 -servername auto.vmw.lab 2>/dev/null | openssl x509 -noout -subject -enddate"

# Check that several hosts resolve from Orchestrator
bash -c "for h in auto.vmw.lab vc.vmw.lab; do getent hosts $h >/dev/null && echo $h resolves || echo $h NOT resolving; done"

Two cautions when you take this beyond a lab:

  • -k skips certificate checks. That’s fine for a lab with self-signed certificates, but in production point curl at your CA bundle with --cacert instead.
  • Don’t put passwords or tokens in the command string. The action logs the command it runs. Pass secrets as separate SecureString inputs and build the request in Python, or better still, use Python’s own HTTP libraries for authenticated calls.

Summary

  • In Orchestrator 9.1.1, JavaScript can no longer reach java.lang.Runtime or java.lang.ProcessBuilder, so Runtime.exec fails with getRuntime is not a function or Cannot find function exec.
  • Move OS commands into a Python action that uses subprocess.
  • Commands run inside the vco-app pod, which has tools such as curl, jq and openssl.
  • For pipes and other shell features, wrap the command in bash -c "...".

Thanks to Stanislav Todorov for suggesting the Python approach.


Discover more from Cloud Blogger

Subscribe to get the latest posts sent to your email.