#564

macOS/SIGSEGV: build-time Cottontail intermittently crashes at JSC bootstrap when spawned by Hutch (`electrobun build`/`dev`)

OpenSquallLeonhart13opened 1mo ago

Summary

Electrobun 2.x's build-time Cottontail runtime intermittently SIGSEGVs at JSC bootstrap when spawned by Hutch during electrobun build / electrobun dev. The crash aborts before any config or bundling runs, so electrobun build --env=dev|stable and electrobun dev fail with CottontailBuildFailed. It is not project-specific and is not fixed by the newest beta pairing.

Environment

  • macOS (darwin arm64), Apple Silicon
  • Pairing A (stable): electrobun@2.0.1 ↔ hutch@0.24.3 ↔ cottontail@0.5.0
  • Pairing B (beta): electrobun@2.0.2-beta.35 ↔ hutch@0.27.0 ↔ cottontail@0.7.0
  • Both pairings reproduce. (Cottontail binaries run standalone; cottontail --version and a trivial cottontail build file.ts both succeed.)

Repro

From any Hutch/Electrobun 2.x project:

./node_modules/.bin/electrobun build --env=dev
# or
./node_modules/.bin/electrobun dev
# or
bun run dev   # electrobun dev --watch

Output:

cottontail: fatal runtime signal SIGSEGV
version: 0.5.0
platform: darwin arm64
error: CottontailBuildFailed
error: script "dev" exited with code 1

No esbuild/TS/config diagnostic is emitted — it is a native crash, not a build error.

Minimal repro (no project work happens at all): hutch electrobun build --help also segfaults. That isolates it to Cottontail's bootstrap during Hutch invocation, below the JS config layer.

Frequency

Intermittent, and the rate is unstable across sessions:

  • Earlier measurement on pairing A: ~2/12 fail (PASS=10 FAIL=2), same tree.
  • Beta pairing B: ~1/6 fail.
  • Most recent session: 9/9 fail (both bun run dev and bare ./node_modules/.bin/electrobun dev).

Because it passes sometimes, it initially looked like dev worked while build didn't.

Crash signature

.ips reports (byProc: cottontail): EXC_BAD_ACCESS / SIGSEGV, subtype KERN_INVALID_ADDRESS, termination Segmentation fault: 11.

Faulting thread (representative, pairing A / cottontail 0.5.0):

__pthread_kill
pthread_kill
raise
cottontail  uv_wtf8_to_utf16
cottontail  std::__1::__call_once_proxy[abi:ne210100]<std::__1::tuple<void (&)()>>(void*)
cottontail  vmEntryHostFunction
cottontail  llintPCRangeStart
cottontail  JSC::mapIteratorPrivateFuncMapIteratorValue(...)
cottontail  JSC::iteratorHelperPrivateFuncCreate(...)
cottontail  JSC::SyntheticModuleRecord::destroy(...)
cottontail  JSC::WebAssemblyModuleRecord::destroy(...)

The abort originates in Cottontail's JSC/VM bootstrap (raise() after a wild pointer), via a C++ std::call_once once-init, running JIT'd JS iterators. No esbuild / bundle-copy / config symbols appear anywhere in the stack. Pairing B reports show the same KERN_INVALID_ADDRESS abort with cottontail_jsc_get_class_definition_empty in the stack — the fault survives the 0.5.0 → 0.7.0 jump.

Disproven (worth not re-investigating)

  • Stale per-PID state / locks — with all hutch processes killed and .cottontail-tmp/, ~/.hutch/state/locks/graph.lock, the project lock, and .hutch/locks/electrobun-build.lock all deleted, the next build still crashed identically.
  • Version mismatch — hutch --version → 0.24.3, correctly paired with cottontail 0.5.0 (rev e5660061b8e64b5ea044799da8518780a9987391); the Electrobun release manifest declares cottontail.defaultVersion: "0.5.0".
  • hutch sync / upgrade / --env=stable — same toolchain, same crash.
  • Pristine checkout — reproduces from a clean tree.
  • Project source — hutch electrobun build --help segfaults, so no project code is involved.

Impact

Blocks electrobun build and any publish/CI step that runs it (prepack). The dev workaround we used is electrobun run against a previously built bundle, which depends on a prior successful build.

We worked around it for now by pinning electrobun@1.18.1 (no Hutch/Cottontail in the build path), so we're unblocked, but we can't adopt 2.x until this is fixed.

Ask

A fix in Cottontail's JSC bootstrap, or a Hutch/Cottontail pairing known to be stable. Happy to attach a full .ips or run any instrumented build to help narrow it.

  • Crash reports on disk: ~/Library/Logs/DiagnosticReports/cottontail-*.ips (19+ reports, all the same faulting-thread signature)

Activity

2
tmgreen22d ago

Possibly related evidence from the current stable pairing: Electrobun 2.0.2, Hutch 0.27.1, packaged Cottontail 0.7.1, macOS 26.6.2 (25G83), arm64. Latest supported releases were rechecked before investigation.

This occurred in a standalone packaged runtime spawned by Python, rather than Hutch's build-time runtime. After 44 serial safety stages passed, starting our unchanged schema test entry terminated with SIGSEGV in 90 ms, with no test output. The full run remains failed; no automatic retry was added.

Invocation, from the repository root after packaging:

NODE_PATH="$PWD/node_modules" \
APAK_VALIDATOR="file://$APP/Contents/Resources/app/validation/validation-process.mjs" \
APAK_TEST_BACKEND="file://$APP/Contents/Resources/app/scala/backend-fastopt/main.js" \
sandbox-exec -p '(version 1)(allow default)(deny network*)' \
  "$APP/Contents/MacOS/cottontail" "$PWD/tests/schemas.test.ts"

APP is the absolute path to the retained production app. The runtime reports 0.7.1; SHA-256 is 84428a0f30babd9a1aedfcb5b75b0027e6ea53cfd5153d80e593da745cc65822.

The system crash report records:

  • Process lifetime about 80 ms; faulting thread 0.
  • EXC_BAD_ACCESS, SIGSEGV, KERN_INVALID_ADDRESS at 0x0000000000000010.
  • Runtime image UUID 86502F03-79C4-3EC2-BDB3-ED7C344F8460.
  • First image-relative frame offsets: 0x271d488, 0x271c75c, 0x271a570, 0x2719cb4, 0x2713da0, 0x21c2f8c, 0x2751d8c, 0x2736d7c.
  • The stripped binary's nearest symbol for these frames is std::__1::__call_once_proxy[abi:ne210100]<std::__1::tuple<void (&)()>>(void*), with large offsets; these names alone do not establish the root cause.

Explicit diagnostics afterward: the schema test passed both directly and through a JavaScript entry wrapper. 100 serial JavaScript-wrapper module-load probes and 100 direct TypeScript adapter-import probes also passed. We cannot reproduce the crash on demand and do not claim a runtime fix. No application data loss was observed. The failed gate, sanitized crash excerpt, commands and every probe outcome are retained locally.

tmgreen22d ago

A second complete run on the same current supported pairing (Electrobun 2.0.2 / Hutch 0.27.1 / Cottontail 0.7.1, same binary hash as above) stopped after 50 passing stages. This time the standalone packaged runtime terminated with SIGSEGV 83 ms into unchanged tests/validation.test.ts, with no output. The schema stage that failed in the first run passed this time. Neither failed run is treated as green, and no automatic retries were added.

The command is the same as above with tests/validation.test.ts as the entry. No new system .ips was found for this second crash, so I cannot claim matching native frames. Second failed report retains the exact command, exit code, empty-output hash and all preceding stages; the first failed report is also retained.

Additional bounded diagnostics: 500 schema and 500 validation startup probes loaded the full original TypeScript test modules (adapter imports changed to absolute paths), then exited before test bodies or filesystem work; 1,000/1,000 passed. The original validation test also passed through a JavaScript entry wrapper that logs before importing it. Together with the earlier 200 adapter-import probes, this is 1,200 startup probe passes, without reproducing the native failure on demand. The complete native app/webview workflow passed separately on fresh disposable dictionaries.

Diagnostic evidence distinguishes these passes from full-gate readiness. No root cause, runtime fix or application data loss is established. This may be related to the bootstrap failure in this issue, but the shared cause is still unconfirmed.