typescript bindings#31
Merged
Merged
Conversation
The test script passed a quoted glob ("test/*.test.mjs") to `node --test`.
Node only expands glob patterns itself in v21+; on the CI-pinned Node 20 the
quoted string is treated as a literal path, so `node --test` found zero files
and exited 1 — failing ts-build before any test ran. It passed locally only
because dev machines run Node 22.
Dropping the quotes lets the shell expand the glob into explicit file paths,
which `node --test` has accepted since v18 — version-independent, and it keeps
common.mjs (a non-test helper) out of the run. Verified: 28 tests now
discovered across all 8 suites (6 pass no-model tier, 22 skip without a canary).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The buffered-stream test passed chunkMs: 2000, which the canary model converts to 25 encoder frames — outside its allowed chunk menu (1,2,7,13) — so transcribe_stream_begin rejected it with INVALID_ARG. (chunkMs: 2000 is the value the Python suite uses only in a struct-plumbing unit test, never fed to a real stream; its streaming test uses defaults.) Drop the override so the request uses the in-menu defaults (L=70, R=13), matching the Python streaming test. Stream-slot field plumbing stays covered by the cache-aware test's attContextRight override. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Node 20 reached end-of-life (~2026-04-30) — no further security updates — so
the binding shouldn't advertise or test against it. Node 22 is the current
supported LTS and what dev machines already run; aligning CI to it also closes
the dev/CI drift that let the Node-21+ test-runner glob behavior slip past CI.
- engines.node and the lockfile mirror: >=20 -> >=22
- typescript-ci.yml (ts-gates, ts-build) and publish.yml (ts-rehearsal,
ts-release): setup-node 20 -> 22
- README runtime-support + resource-management notes: Node 20 -> 22
Single-version 22 rather than a 20+22 matrix: the floor should match what we
test, and testing an EOL runtime defeats the bump. Unrelated node20 mentions in
cuda-windows.yml / python-wheels.yml are about an upstream action runtime and
are left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These two jobs are the per-PR time sinks, and the time is CPU-bound model
inference, not compilation:
- rust-ci linux-{static,shared,dynamic-backends}: ~42-45 min each, every PR.
The build step is ~8s (ccache warm); the cost is `cargo test -p
transcribe-cpp` running the canaries on CPU. ~34 min of that is the
moonshine-streaming suite alone (full jfk fed in 100 ms chunks, 3 tests).
- python-bindings python-shared: ~18 min, same inference profile.
The library autodetects threads when n_threads==0 (the default everywhere):
hardware_concurrency() for batch/mel, min(8, cores) for the parakeet decoder —
so 8 vCPUs is the sweet spot (decode caps at 8; inference is memory-bandwidth-
bound past that). No test pins a thread count, so the extra cores get used.
Compile-bound release jobs (python-wheels) are a separate, later bump to 16.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 8-vcpu bump did not help: the per-PR cost is dominated by the moonshine streaming conformance, which is latency/oversubscription-bound (small per-step matmuls on a tiny model + cargo running the heavy tests concurrently), not throughput-bound — so more cores don't shorten it. Reverting to 2-vcpu to stop paying for capacity that isn't used. The real fixes (rust posture split so the model tier runs on one leg, and demoting the other bindings to FFI smoke) are tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 8-leg posture matrix re-ran the full model-tier conformance + examples on every leg, but shared / dynamic-backends differ from static only in LINK posture — the FFI marshalling code is byte-identical, so a marshalling regression can't hide on one posture and not another. Running the heavy model tier (chiefly the ~30 min moonshine streaming conformance on the 2-vcpu linux legs) on all of them re-certified the same native compute up to 8×. Gate the canary fetch + the examples on a new matrix `model_tier` flag, true only on the representative *-static leg of each OS. The *-shared and *-dynamic-backends legs now run build + the -sys FFI smoke + the always-on no-model tier (which still exercises the dynamic-backends module load via init_backends_default) and skip the canary, so their model-gated tests self-skip. No posture coverage lost; ~2 redundant ~42 min linux legs/PR gone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…teardown crash The log callback is registered eagerly during native bootstrap and lives for the whole process, but nothing detached it at teardown. On Windows the library unload + ggml worker-thread teardown then raced the koffi trampoline's destruction, exiting the test subprocess with an access violation (0xC0000005) after every subtest passed — failing the windows-x64 conformance leg on family.test.mjs and pcm.test.mjs (the model-tier files that spin up worker threads). Register a process exit hook that calls transcribe_log_set(null) — dropping the native reference before the unload — then koffi.unregister, mirroring the install/uninstall pairing the per-run abort callback already uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Windows teardown crash" This reverts commit a5d666a.
…egfault A dlopen'd backend module (the cpu-vulkan Linux/Windows builds; compiled-in macOS metal never dlopens, which is why it never reproduced) can emit a diagnostic during process teardown. Routed through the transcribe log sink into the koffi callback trampoline after koffi has freed it, that segfaults the process at exit after every test/example has already passed — failing the windows-x64 (and, intermittently, linux-x64) conformance/examples legs. The log callback is the only persistent native->JS callback alive at exit (the abort callback is per-run and unregister-paired), so detaching logging detaches the whole surface. Add an internal exported transcribe_shutdown() — disable the sink and detach ggml's logger — and call it from a process-exit handler. It is exported but intentionally NOT in the public header: it has no stable 0.x lifecycle contract, the bug is specific to the koffi callback lifetime, and keeping it out of the header avoids PUBLIC_HEADER_HASH churn across all bindings. The exit hook binds it opportunistically and falls back to logSet(null) if a hand-pointed older library predates the symbol; it never koffi.unregisters (that frees the trampoline while a worker thread may still hold it — the use-after-free that a previous attempt introduced on Linux). Registered before initBackends so a failed backend init still detaches cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows/Linux teardown segfault needs the koffi log-callback trampoline and ggml worker threads to coexist in one process: no-model processes carry the trampoline (they call setLogHandler) yet never crash, because they spin up no worker threads. Eager registration at native() bootstrap forced every model-loading process to carry the trampoline, creating that coexistence — and detaching the sink at exit (prior commit) does not fix it, because it is the trampoline's lifetime racing the dlopen'd-backend/worker-thread teardown, not the log emission, that crashes. Install the trampoline lazily on the first setLogHandler(handler) instead, so a process that never opts into logging never carries it — model load + transcribe stays clear of the race. A process that only disables logging gets native silence without a trampoline. The transcribe_shutdown() exit hook from the prior commit stays as best-effort mitigation for the one remaining case: a host that sets a handler and then transcribes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tstrap" This reverts commit e538066.
…ardown segfault" This reverts commit 5b21e43.
…MP exit crash is fixed The windows-x64 ts-build leg has never passed: loading a model on Windows spins up the ggml CPU threadpool, and the MSVC OpenMP (vcomp) runtime crashes (0xC0000005) at process exit under node/koffi, after the run succeeds. That is a native teardown bug, orthogonal to the binding, and needs a real Windows machine to fix. Skip the canary fetch on Windows so the model-gated tests and the model-loading examples self-skip cleanly. The leg still runs its reason for existing — the no-model koffi DLL-resolution + ABI + error-mapping tier — and gates on it. One guard to drop to re-enable the model tier once the teardown crash is fixed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.