fetch() in Run Real JavaScript leaks ~10KB per call (even when the request fails)

Hey Andreas,

I've been chasing BTT memory creeping up for a few weeks now. I run a couple of long Run Real JavaScript scripts that poll a small local HTTP server, around 1000 requests a minute, and BTT grows by roughly 1.2GB an hour until i restart it. Idle BTT is totally fine, it sat at ~66MB the whole time i watched it.

So I measured each call on its own: 3000 calls in a loop, footprint -p before and after, then waited to see if anything came back. Nothing did.

  • fetch, tiny response: ~12KB per call
  • fetch, 17KB response: ~34KB per call
  • fetch, body never read (just r.status): ~11KB per call
  • fetch to a port with nothing listening: ~10KB per call
  • set/get variables, get_variable_type, setTimeout, a 0s pause action and webserver reads from outside: basically 0

Easiest repro, no server needed:

async function leak() {
  for (let i = 0; i < 3000; i++) {
    await fetch('http://127.0.0.1:1/').catch(() => null);
  }
  return 'done';
}

Run that as a Run Real JavaScript action and BTT goes up ~30MB and stays there.

I think I see why. In the fetch polyfill (String(fetch)), request.onload is a closure that captures request, so the native XMLHttpRequest holds a JS function that points back at itself and I'm guessing JSC can't collect that loop. A plain XMLHttpRequest written the same way leaks the same ~13KB. When I null out onload/onerror after the request finishes it drops to ~3.5KB per call, but then requests start stalling for seconds at a time, so I don't have a workaround on my end. Probaly needs a fix on the native side.

Setup: BTT 6.885 (build 2026100205), macOS 26.6.2, M4 Pro MacBook Pro. I only tested with the isolated JS context turned off.

Happy to run a test build or grab samples if that helps. Thanks for BTT btw, I use it for pretty much everything.

thanks for reporting, I'll add a small fix for the next build