Skip to content

fix: handle ps.tree() failure gracefully in kill() on Alpine Linux#1441

Open
Luc0-0 wants to merge 2 commits intogoogle:mainfrom
Luc0-0:fix/kill-bustbox-ps-parse-error
Open

fix: handle ps.tree() failure gracefully in kill() on Alpine Linux#1441
Luc0-0 wants to merge 2 commits intogoogle:mainfrom
Luc0-0:fix/kill-bustbox-ps-parse-error

Conversation

@Luc0-0
Copy link
Copy Markdown

@Luc0-0 Luc0-0 commented Mar 25, 2026

Problem

On Alpine Linux (and other environments using BusyBox), kill() throws an unexpected TypeError instead of killing the process:

TypeError: Cannot read properties of undefined (reading 'spaces')
    at getBorders (vendor-core.cjs)
    at parseUnixGrid (vendor-core.cjs)

This was introduced in v8.8.4 when @webpod/ps replaced the previous process tree implementation. BusyBox ps produces output in a format that @webpod/ingrid cannot parse — it returns an empty array, causing lines[0].spaces to throw.

Reported in #1369.

Fix

Wrap the ps.tree() loop in a try/catch in src/core.ts. If the process tree lookup fails (e.g. due to an unrecognised ps output format), execution falls through to the existing process.kill(pid) call below, which still terminates the target process. Child processes may not be killed in degraded environments, but the caller no longer receives an unexpected error.

// Before
for (const p of await ps.tree({ pid, recursive: true })) {
  try { process.kill(+p.pid, signal) } catch (e) {}
}

// After
try {
  for (const p of await ps.tree({ pid, recursive: true })) {
    try { process.kill(+p.pid, signal) } catch (e) {}
  }
} catch (e) {
  // ps.tree() can fail on non-standard ps implementations (e.g. BusyBox on
  // Alpine Linux). Fall through to direct process.kill() below.
}

The root cause is in @webpod/ingrid's parser — a fix there would be more complete — but this makes kill() robust against any future parser failures as well.

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 25, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

On Alpine Linux (and other environments using BusyBox), the ps output
format differs from standard GNU/procps ps. The @webpod/ingrid parser
used by @webpod/ps cannot parse BusyBox output and throws a TypeError
when kill() attempts to collect child processes via ps.tree().

Wrap the ps.tree() loop in a try/catch so that parse failures fall
through to the direct process.kill() call below. The parent process
is still killed; child processes may survive in degraded environments,
but the caller no longer receives an unexpected TypeError.

Fixes google#1369
@Luc0-0 Luc0-0 force-pushed the fix/kill-bustbox-ps-parse-error branch from bd21d3d to 3de8c66 Compare March 25, 2026 16:20
@antongolub
Copy link
Copy Markdown
Collaborator

This is not a fix, but a workaroud. We should figure out what exactly goes wrong with BusyBox + ps.

@Luc0-0
Copy link
Copy Markdown
Author

Luc0-0 commented Mar 26, 2026

Traced it — getBorders() in @webpod/ingrid hits lines[0].spaces without checking for empty input. BusyBox ps doesn't support -lx so @webpod/ps gets back empty/garbage output, parseLines() returns [], and boom — TypeError on undefined.

Root fix would be a if (lines.length === 0) return [] guard in getBorders(), and maybe having @webpod/ps detect BusyBox and use ps -o pid,ppid,comm instead of ps -lx.

Kept the try/catch here so kill() doesn't throw a random TypeError when the process is perfectly killable via process.kill() directly. Updated the comment to point at the actual source. Can open PRs on ingrid/ps if you'd prefer fixing it there.

@Luc0-0
Copy link
Copy Markdown
Author

Luc0-0 commented Mar 26, 2026

Opened the upstream fixes btw:

Once those land and get re-vendored, the try/catch here becomes redundant. Until then it keeps kill() from blowing up on Alpine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants