stablyai/orca · error · Error

Electron did not expose GC; keep --js-flags=--expose-gc in t

Error message

Electron did not expose GC; keep --js-flags=--expose-gc in the harness

What it means

Thrown by forceGc() in the hang-watchdog memory benchmark when `global.gc` is not a function. V8 only exposes `global.gc` when the process is started with `--js-flags=--expose-gc`. The benchmark calls forceGc() to flush unreferenced objects before sampling RSS, so accurate memory deltas depend on a real GC. This is a harness-integrity guard: runTrial always launches Electron with `--js-flags=--expose-gc`, so hitting it means the flag was stripped or overridden.

Source

Thrown at config/scripts/hang-watchdog-memory-benchmark.mjs:42

const VERIFY_CHECK_INTERVAL_MS = 50
const VERIFY_BLOCK_MS = 1_200
const PRODUCTION_HEARTBEAT_INTERVAL_MS = 2_000
const PRODUCTION_TIMEOUT_MS = 45_000
const PRODUCTION_CHECK_INTERVAL_MS = 5_000
const PRODUCTION_SAMPLE_MS = 30_000
const MAX_LAUNCH_ATTEMPTS = 3
const MIB = 1024 * 1024
const scriptPath = import.meta.filename
const repoRoot = path.resolve(import.meta.dirname, '..', '..')
const entryPath = path.join(repoRoot, 'out', 'main', 'main-thread-hang-watchdog-entry.js')

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

function forceGc() {
  if (typeof global.gc !== 'function') {
    throw new Error('Electron did not expose GC; keep --js-flags=--expose-gc in the harness')
  }
  global.gc()
  global.gc()
}

function blockMainThread(ms) {
  const startedAt = Date.now()
  while (Date.now() - startedAt < ms) {
    // Intentional synchronous stall.
  }
  return { startedAt, endedAt: Date.now() }
}

function readMarker(markerPath) {
  try {
    return JSON.parse(readFileSync(markerPath, 'utf8'))
  } catch {
    return null

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Run the benchmark through its public entry (runBenchmark), which spawns Electron with `--js-flags=--expose-gc` automatically.
  2. If invoking the internal path manually, launch Electron with the flag: `electron --js-flags=--expose-gc <launcher>`.
  3. Verify the flag took effect: `node --expose-gc -e 'console.log(typeof global.gc)'` should print `function`.
  4. On Electron upgrades, confirm the flag is still honored by the bundled V8.

Example fix

// before: internal path without the flag
ELECTRON_RUN_AS_NODE=1 node config/scripts/hang-watchdog-memory-benchmark.mjs
// after: go through runBenchmark, which adds --expose-gc
node config/scripts/hang-watchdog-memory-benchmark.mjs --boundary child --trials 7
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on forceGc, confirm the flag took effect
function assertGcExposed() {
  if (typeof global.gc !== 'function') {
    throw new Error('start with --js-flags=--expose-gc; global.gc is missing')
  }
}
assertGcExposed()

Type guard

const isGcExposed = (): boolean => typeof global.gc === 'function'

Prevention

When it happens

Trigger: Calling forceGc() in a process launched without --expose-gc — e.g. running the script directly under plain Node, under Electron with a custom ELECTRON_RUN_AS_NODE invocation that drops the flag, or after an Electron version change that altered flag passthrough.

Common situations: Manually invoking the internal path (`ORCA_HANG_WATCHDOG_BENCH_INTERNAL=1`) without the flag, debugging by running `node config/scripts/hang-watchdog-memory-benchmark.mjs` directly, or an Electron upgrade that silently ignores unknown --js-flags.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/c32056ac44094f27. Report an issue: GitHub.