Skip to content

Fix Harness process lingering after tests when diagnostics are enabled#159

Merged
V3RON merged 1 commit into
mainfrom
fix-diagnostics-instrumented-subprocess
Jul 14, 2026
Merged

Fix Harness process lingering after tests when diagnostics are enabled#159
V3RON merged 1 commit into
mainfrom
fix-diagnostics-instrumented-subprocess

Conversation

@V3RON

@V3RON V3RON commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What is this?

This PR fixes a hang where the Harness process kept running after all tests finished whenever diagnostics were enabled (via the diagnostics config option or the RN_HARNESS_DIAGNOSTICS env var). Runs with diagnostics off exited cleanly; the same runs with diagnostics on lingered until killed manually or by a CI timeout.

How does it work?

When diagnostics are enabled, platform modules like adb and simctl are wrapped with the instrumented() proxy to record timing spans. The wrapper treated every thenable result as an ordinary promise and returned result.then(...) — but calling .then() on a thenable always produces a brand-new plain Promise, discarding any extra shape the original object had.

That's fatal for nano-spawn's Subprocess return value (used by adb.startLogcat and simctl.launchAppProcess), which is a promise plus nodeChildProcess and Symbol.asyncIterator. With those stripped, teardown's stopSubprocess() found nodeChildProcess === undefined, the kill attempt threw into an intentionally silent catch, and the background adb logcat / app-launch OS processes survived — keeping Node's event loop alive after the run finished. Log streaming via for await over the subprocess broke the same way.

The wrapper now records span completion as a side-effect .then() and always returns the original thenable untouched, so subprocess handles (and any other hybrid thenables) pass through instrumentation with their shape intact. A regression test covers the hybrid-thenable case, and a version plan is included.

Why is this useful?

  • Harness runs with diagnostics enabled now terminate cleanly instead of hanging until a manual kill or CI timeout, so diagnostics can be left on in CI without risk.
  • Background adb logcat and app-launch subprocesses are reliably killed during teardown, so log streaming and crash detection behave identically with and without diagnostics.
  • Instrumentation is now shape-preserving by construction, preventing the same silent-stripping bug for any future API that returns an augmented promise.

…is enabled

With diagnostics enabled, instrumented() collapsed every thenable through
.then(), turning nano-spawn Subprocess objects (Promise & AsyncIterable &
{ nodeChildProcess }) into plain promises. Teardown's stopSubprocess()
then found no nodeChildProcess to kill, so background adb logcat /
app-launch processes survived and kept the harness process alive after
tests finished. Track spans as a side effect and return the original
thenable untouched.
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-harness Ready Ready Preview, Comment Jul 14, 2026 7:53am

Request Review

@V3RON V3RON merged commit d098d15 into main Jul 14, 2026
6 checks passed
@V3RON V3RON deleted the fix-diagnostics-instrumented-subprocess branch July 14, 2026 10:48
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.

1 participant