Repository navigation
node:diagnostics_channel: preserve non-callable bindStore transform errors #3085
Description
Summary
Channel#bindStore(store, transform) should remember any non-undefined transform value. During runStores(), Node attempts to call that transform; if it is not callable, the callback still runs with no store context and Node reports an uncaught TypeError: transform is not a function. Perry currently discards any non-closure transform at bind time, so null, numbers, strings, and other non-callable transform values behave exactly like an omitted transform and no error is reported.
Node Behavior
Probe run with Node v25.9.0:
const { channel } = require("node:diagnostics_channel");
const { AsyncLocalStorage } = require("node:async_hooks");
process.on("uncaughtException", (err) => {
console.log("uncaught:", err.name, err.code || "no-code", err.message);
process.exitCode = 0;
});
for (const transform of [null, 1]) {
const ch = channel("bind-transform-" + Math.random());
const als = new AsyncLocalStorage();
ch.bindStore(als, transform);
const ret = ch.runStores({ value: 1 }, () => {
console.log("callback store", als.getStore());
return "ret";
});
console.log("returned", ret);
}Observed behavior: the callback runs and returns, als.getStore() is undefined, and Node emits uncaught TypeError events with message transform is not a function for both null and numeric transform values. An omitted or explicit undefined transform does not report that error.
Perry Behavior
crates/perry-runtime/src/node_submodules/diagnostics.rs::diag_channel_bind_store()computeslet transform = if valid_closure_value(transform) { Some(transform) } else { None };.- That means every non-callable transform is stored as
None, indistinguishable from an omitted transform. diag_channel_run_stores()only attemptsjs_closure_call1(closure_ptr(t), data)when the stored transform isSome(t), so the Node TypeError path is skipped for non-callable transform values.- Current store fixtures cover a valid transform and a throwing function transform, but not non-callable transform values.
Suggested PR Cut
Batch this with related issues in:
node:diagnostics_channel: tracing and store semantics parity cut (#3242)
Good batch candidates:
- node:diagnostics_channel: forward all runStores callback arguments #3082
- node:diagnostics_channel: implement Channel.withStoreScope disposable scopes #2632
- node:diagnostics_channel: honor traceCallback position and arguments #3086
Do not batch with:
- unrelated AsyncLocalStorage semantics outside diagnostics store binding
- worker/thread diagnostics subscriber policy changes
- tracing-channel category work in
node:trace_events
Acceptance
- parity/regression test proves
bindStore(store, null)followed byrunStores()reports Node-compatible non-callable transform error behavior - parity/regression test proves a numeric or string transform has the same error path
- omitted or explicit
undefinedtransform remains the no-transform case - wrapped callback execution and return value behavior remain Node-compatible
- related known-failure/docs/manifest entries updated if touched
Activity
andrewtdiz commentedon May 31, 2026
Taking this in a diagnostics_channel tracing/store semantics PR cut with #3082, #3086, and #3084.
andrewtdiz commentedon May 31, 2026
PR opened: #3503
andrewtdiz commentedon May 31, 2026
Implemented in #3504.
Verification on the rebased branch:
PATH=/home/github-runner/actions-runner/externals/node24/bin:$PATH ./run_parity_tests.sh --suite node-suite --module diagnostics_channelpassed: 64 pass, 0 fail, 0 compile fail.RUSTC_WRAPPER= cargo check -p perry-runtime -p perry-codegen -p perry-hir -p perry-api-manifest -p perrypassed with existing warnings.cargo fmt --all -- --check,git diff --check && git diff --cached --check,./scripts/check_file_size.sh, andjq empty test-parity/known_failures.jsonpassed.test(parity): retire the stale known-failure entry for the diagchanne…
test(parity): retire the stale known-failure entry for the diagchanne…