#2189·hermes

ISerialization hits "Too many handles allocated in GCScope" on small object arrays

Author: mrousavyCreated Sep 18, 2026Updated Sep 18, 2026

I'm using jsi::ISerialization to parse JSON in a background Hermes runtime and then move the result to another runtime. With JSI_UNSTABLE enabled, I hit this assertion in a debug build:

Assertion failed: (getHandleCountDbg() < handlesLimit_ &&
"Too many handles allocated in GCScope"),
function newPinnedHermesValue, file HandleRootOwner.h, line 440.

I first ran into it with a roughly 1 MB JSON response, but it turns out a much smaller input is enough. An array of ten objects with five properties each fails during serialization. With five objects, serialization succeeds, but deserialization hits the same assertion. One object works.

I reproduced this in standalone C++, without React Native, Swift, or networking. This is Hermes 260318099.0.3, revision 7e5dfcd59b58d87aad5ea4834dc31355374f3c1c, on macOS arm64. I also hit it in an iOS arm64 simulator. Hermes and the caller are built with JSI_UNSTABLE, and NDEBUG is not defined.

Here's the reproducer:

cpp
#include <hermes/hermes.h>
#include <jsi/jsi.h>
#include <string>

int main() {
  auto runtime = facebook::hermes::makeHermesRuntime();
  auto* serializer =
      facebook::jsi::castInterface<facebook::jsi::ISerialization>(runtime.get());
  if (!serializer) return 1;

  std::string json = "[";
  for (int i = 0; i < 10; ++i) {
    if (i) json += ",";
    json += R"({"albumId":1,"id":1,"title":"test","url":"https://example.com","thumbnailUrl":"https://example.com"})";
  }
  json += "]";

  auto value = facebook::jsi::Value::createFromJsonUtf8(
      *runtime, reinterpret_cast<const uint8_t*>(json.data()), json.size());
  auto serialized = serializer->serialize(value); // Asserts with 10 objects.
  auto copy = serializer->deserialize(serialized); // Asserts with 5 objects.
}

The JSON parse succeeds; the failure is in structured cloning. The serialization stack goes through serializeProperties, JSObject::getOwnComputedDescriptor, valueToSymbolID, and stringToSymbolID before hitting the handle limit.

It looks like the property loops in SerializedValue.cpp accumulate temporary handles in the outer GCScope. The default debug limit is 48. I've tried adding a per-iteration GCScope to the serialization/deserialization loops for objects, arrays, maps, and sets. That fixes the reproducer and a 5,000-object round trip locally, with the assertion still enabled.

I'd expect these values to round-trip without hitting the temporary-handle limit. Happy to send a PR with the fix and regression tests.