nodejs/node · error · Exception

%s: exit status %d

Error message

%s: exit status %d

What it means

Raised by RunCmd in tools/inspector_protocol/roll.py when a subprocess launched via subprocess.Popen returns a non-zero exit code. roll.py is the script that rolls (syncs) a vendored copy of the inspector_protocol dependency into a host repo (e.g. Chromium/devtools-protocol); RunCmd wraps every git/external command and treats any failure as fatal. NOTE: the raise is itself buggy - it uses 'raise Exception(fmt, args...) with a comma instead of 'raise Exception(fmt % args)', so the formatted message is never produced; the exception carries the raw tuple as .args.

Source

Thrown at tools/inspector_protocol/roll.py:37

    'BUILD.gn',
    'check_protocol_compatibility.py',
    'code_generator.py',
    'concatenate_protocols.py',
    'convert_protocol_to_json.py',
    'inspector_protocol.gni',
    'README.md',
    'LICENSE',
    'pdl.py',
]

REVISION_LINE_PREFIX = 'Revision: '

def RunCmd(cmd):
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
  (stdoutdata, stderrdata) = p.communicate()
  if p.returncode != 0:
    raise Exception('%s: exit status %d', str(cmd), p.returncode)
  return stdoutdata.decode('utf-8')


def CheckRepoIsClean(path):
  os.chdir(path)  # As a side effect this also checks for existence of the dir.
  # If path isn't a git repo, this will throw and exception.
  # And if it is a git repo and 'git status' has anything interesting to say,
  # then it's not clean (uncommitted files etc.)
  if len(RunCmd(['git', 'status', '--porcelain'])) != 0:
    raise Exception('%s is not a clean git repo (run git status)' % path)


def CheckRepoIsInspectorProtocolCheckout(path):
  os.chdir(path)
  revision = RunCmd(['git', 'config', '--get', 'remote.origin.url']).strip()
  if (revision != 'https://chromium.googlesource.com/deps/inspector_protocol.git'):
    raise Exception('%s is not a proper inspector_protocol checkout: %s' % (path, revision))

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Reproduce the failing command manually (the exception's args tuple contains str(cmd)) and read the real stderr.
  2. Fix the underlying git/remote issue: configure credentials, fix the proxy, or correct the SHA.
  3. Ensure the working tree is clean (git status) and the upstream remote URL is reachable (git ls-remote).
  4. Patch the raise to use '%' formatting so the error message is actionable: raise Exception('%s: exit status %d' % (str(cmd), p.returncode)).

Example fix

// before
def RunCmd(cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    (stdoutdata, stderrdata) = p.communicate()
    if p.returncode != 0:
        raise Exception('%s: exit status %d', str(cmd), p.returncode)
    return stdoutdata.decode('utf-8')
// after
def RunCmd(cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdoutdata, stderrdata) = p.communicate()
    if p.returncode != 0:
        raise Exception('%s: exit status %d: %s' % (
            str(cmd), p.returncode, stderrdata.decode('utf-8', 'replace')))
    return stdoutdata.decode('utf-8')
Defensive patterns

Strategy: try-catch

Validate before calling

import subprocess
def check_cmd(cmd):
    p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if p.returncode != 0:
        raise RuntimeError(f'{cmd} failed: {p.stderr.decode("utf-8","replace")}')
    return p.stdout.decode('utf-8')
# call this instead of RunCmd for actionable errors

Type guard

def cmd_will_succeed(cmd) -> bool:
    import subprocess
    p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return p.returncode == 0

Try / catch

try:
    out = RunCmd(cmd)
except Exception as e:
    # e.args is a tuple (fmt, cmd, code) because of the formatting bug;
    # re-run with stderr to diagnose
    import subprocess
    diag = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    raise RuntimeError(f'{cmd} -> {diag.returncode}: {diag.stderr.decode("utf-8","replace")}') from e

Prevention

When it happens

Trigger: Running python roll.py --upstream-sha <sha> when a git command it invokes (clone/fetch/checkout) fails because of network, auth, missing remote, or a non-existent SHA. Any external command in the roll pipeline (git, formatting tools, file operations) exiting non-zero. Running roll.py outside a clean checkout (CheckRepoIsClean raises separately).

Common situations: CI/developer machine lacks credentials to fetch the upstream inspector_protocol repo. The target SHA was force-pushed away. Local modifications to the vendored copy block git operations. Behind a corporate proxy that breaks git fetch. Wrong CWD (the script does os.chdir inside CheckRepoIsClean, masking path issues).

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/61de1f8806b6c63d. Report an issue: GitHub.