[PR #7263] [CLOSED] fix(javascript): keep timer cancellation within Node's handle family #7797

Closed
opened 2026-11-05 01:47:43 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7263
Author: @XiaoBinGan
Created: 10/26/2026
Status: ❌ Closed

Base: main ← Head: fix/timer-cancel-family


📝 Commits (1)

  • 428548b fix(javascript): keep timer cancellation within Node's handle family

📊 Changes

2 files changed (+54 additions, -1 deletions)

View changed files

📝 src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts (+31 -0)
📝 src/domain/javascript/javascriptSemanticAsyncEffects.ts (+23 -1)

📄 Description

Summary

REA reports clearImmediate(setTimeout(work, 10)) as a completed cancellation and the semantic graph emits a resolved cancels-timer relation. In Node the timer keeps running: the four clearTimeout/clearInterval-on-Immediate and clearImmediate-on-Timeout combinations are silent no-ops. This makes timer cancellation respect Node's handle family.

Problem and expected behavior

Node keeps timeouts and immediates in disjoint handle families. clearTimeout and clearInterval are aliases that act only on a Timeout produced by setTimeout/setInterval; clearImmediate acts only on an Immediate produced by setImmediate.

Smallest trigger:

const handle = setTimeout(work, 10);
clearImmediate(handle);

The callback still runs. Measured on Node v24.22.1, all nine pairs:

schedule clearTimeout clearInterval clearImmediate
setTimeout cancelled cancelled runs
setInterval cancelled cancelled runs
setImmediate runs runs cancelled

Node's own lib/timers.js on the same version shows why: clearTimeout requires timer?._onTimeout, clearImmediate requires immediate?._onImmediate and returns early otherwise, and clearInterval is literally clearTimeout.

REA linked a cancellation whenever the cleared binding traced back to any timer schedule, without comparing the two methods. Before this change all nine pairs reported resolution: "complete" with a linkedTimerId, and clearImmediate(setTimeoutHandle) produced a cancels-timer relation with resolution: "resolved" and no timer unknown.

Expected: a cross-family clear establishes no cancellation link. Actual: it established a complete one and the graph asserted it as resolved.

Change and scope

resolveTimers now requires the cancel and the schedule to share a handle family before linking them. A cross-family clear keeps linkedTimerId: null and resolution: "unresolved", so the existing projection reports the relation as a candidate and raises an ambiguous-target timer unknown instead of a resolved fact.

The family split is one small helper over the existing TIMER_SCHEDULE_METHODS / TIMER_CANCEL_METHODS vocabulary; no new fields, relation kinds, or unknowns were added, and same-family behavior is unchanged.

Intentionally not included: any change to setTimeout/setInterval mutual aliasing beyond treating them as one family, and any new resolution state for "known no-op" (an unresolved link with an explicit unknown keeps the gap visible without expanding the contract).

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/domain/javascript/javascriptSemanticAsyncEffects.ts owns timer operations; the change is in resolveTimers, before graph projection. Consumers of timerOperations (graph async projection, reconstruction obligations) see the corrected linkedTimerId/resolution pair and unchanged shapes.
  • Contribution scope fit and linked maintainer decision: not applicable. Ordinary fix within existing boundaries; same class as the accepted fork silent-stdio correction (#7083).
  • CLI and MCP/tool-catalog contract: no schema or tool change. The cancels-timer relation can now be reported as candidate with an ambiguous-target timer unknown where it was previously resolved.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract: resolution/unknown reporting only; observed facts are not removed, and a previously over-claimed relation becomes visibly uncertain.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): none. The fixture is never executed; the Node behavior table was measured separately.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

(The analyzed fixture is parsed, never executed. The separate Node v24.22.1 probe that established the expected behavior ran setTimeout/setImmediate inside the probe process only.)

Evidence and regression coverage

  • Tests added or updated: keeps cancellation within the handle family that Node clears in src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts asserts all nine schedule/cancel pairs' linkedTimerId and resolution.
  • Base reproduction or other evidence: with only the source change reverted and the new test applied, the test fails at the first cross-family pair — AssertionError: setTimeout then clearImmediate: expected { timerId: 'timer:cancel:50:72', …(9) } to match object { kind: 'cancel', …(3) }. With the change, all nine pairs match Node.
  • User-visible CLI/MCP output (if applicable): analyzing a file containing clearImmediate(setTimeout(...)), clearTimeout(setTimeout(...)) and clearInterval(setImmediate(...)) yields 1 resolved cancels-timer relation plus two ambiguous-target timer unknowns, where the same input previously yielded 3 resolved relations and no timer unknown.
  • Remaining proof gaps: Node versions other than v24.22.1 were not probed; the family split is documented behavior in timers.html and stable across supported versions.

For boundary fixes: not applicable — no filesystem, process, or provider boundary is involved; this is a pure IR projection change covered by the domain tests above.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • node --import tsx probe over 9 schedule/cancel pairs, Node v24.22.1 — setTimeout/setInterval cancel with clearTimeout or clearInterval; setImmediate cancels with clearImmediate; the other five keep running
  • node node_modules/vitest/vitest.mjs run src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts — 7 passed
  • node node_modules/vitest/vitest.mjs run --project domain — 8343 passed, 0 failed
  • node node_modules/typescript/bin/tsc --noEmit -p . — exit 0
  • npx oxfmt --check src/domain/javascript/javascriptSemanticAsyncEffects.ts src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts — clean
  • node scripts/rea.mjs analyze-javascript-application <fixture> --format json — 1 resolved cancels-timer, 2 ambiguous-target timer unknowns (previously 3 resolved, 0 unknowns)

Compatibility, safety, and release

  • Breaking changes or migration steps: none. No public schema, tool contract, or CLI flag changes. A graph relation that was previously resolved for a cross-family clear is now candidate with a timer unknown — the correction callers want.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none. Static analysis only; no target interaction.

Author readiness

Human owner: @XiaoBinGan

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/7263 **Author:** [@XiaoBinGan](https://github.com/XiaoBinGan) **Created:** 10/26/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/timer-cancel-family` --- ### 📝 Commits (1) - [`428548b`](https://github.com/morluto/rea/commit/428548b94c12600fe1f1242f84bdd1e56ee29b56) fix(javascript): keep timer cancellation within Node's handle family ### 📊 Changes **2 files changed** (+54 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts` (+31 -0) 📝 `src/domain/javascript/javascriptSemanticAsyncEffects.ts` (+23 -1) </details> ### 📄 Description ## Summary REA reports `clearImmediate(setTimeout(work, 10))` as a completed cancellation and the semantic graph emits a resolved `cancels-timer` relation. In Node the timer keeps running: the four `clearTimeout`/`clearInterval`-on-`Immediate` and `clearImmediate`-on-`Timeout` combinations are silent no-ops. This makes timer cancellation respect Node's handle family. ## Problem and expected behavior Node keeps timeouts and immediates in disjoint handle families. `clearTimeout` and `clearInterval` are aliases that act only on a `Timeout` produced by `setTimeout`/`setInterval`; `clearImmediate` acts only on an `Immediate` produced by `setImmediate`. Smallest trigger: ```js const handle = setTimeout(work, 10); clearImmediate(handle); ``` The callback still runs. Measured on Node v24.22.1, all nine pairs: | schedule | clearTimeout | clearInterval | clearImmediate | | --- | --- | --- | --- | | setTimeout | cancelled | cancelled | **runs** | | setInterval | cancelled | cancelled | **runs** | | setImmediate | **runs** | **runs** | cancelled | Node's own `lib/timers.js` on the same version shows why: `clearTimeout` requires `timer?._onTimeout`, `clearImmediate` requires `immediate?._onImmediate` and returns early otherwise, and `clearInterval` is literally `clearTimeout`. REA linked a cancellation whenever the cleared binding traced back to any timer schedule, without comparing the two methods. Before this change all nine pairs reported `resolution: "complete"` with a `linkedTimerId`, and `clearImmediate(setTimeoutHandle)` produced a `cancels-timer` relation with `resolution: "resolved"` and no timer unknown. Expected: a cross-family clear establishes no cancellation link. Actual: it established a complete one and the graph asserted it as resolved. ## Change and scope `resolveTimers` now requires the cancel and the schedule to share a handle family before linking them. A cross-family clear keeps `linkedTimerId: null` and `resolution: "unresolved"`, so the existing projection reports the relation as a `candidate` and raises an `ambiguous-target` timer unknown instead of a resolved fact. The family split is one small helper over the existing `TIMER_SCHEDULE_METHODS` / `TIMER_CANCEL_METHODS` vocabulary; no new fields, relation kinds, or unknowns were added, and same-family behavior is unchanged. Intentionally not included: any change to `setTimeout`/`setInterval` mutual aliasing beyond treating them as one family, and any new resolution state for "known no-op" (an unresolved link with an explicit unknown keeps the gap visible without expanding the contract). ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/domain/javascript/javascriptSemanticAsyncEffects.ts` owns timer operations; the change is in `resolveTimers`, before graph projection. Consumers of `timerOperations` (graph async projection, reconstruction obligations) see the corrected `linkedTimerId`/`resolution` pair and unchanged shapes. - Contribution scope fit and linked maintainer decision: not applicable. Ordinary fix within existing boundaries; same class as the accepted `fork` silent-stdio correction (#7083). - CLI and MCP/tool-catalog contract: no schema or tool change. The `cancels-timer` relation can now be reported as `candidate` with an `ambiguous-target` timer unknown where it was previously `resolved`. - Provider, bridge, target-format, or platform compatibility: none. - Evidence, artifact, provenance, or reconstruction contract: `resolution`/unknown reporting only; observed facts are not removed, and a previously over-claimed relation becomes visibly uncertain. - Target selection, authority, upstream behavior, and cleanup details (if applicable): none. The fixture is never executed; the Node behavior table was measured separately. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. (The analyzed fixture is parsed, never executed. The separate Node v24.22.1 probe that established the expected behavior ran `setTimeout`/`setImmediate` inside the probe process only.) ## Evidence and regression coverage - Tests added or updated: `keeps cancellation within the handle family that Node clears` in `src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts` asserts all nine schedule/cancel pairs' `linkedTimerId` and `resolution`. - Base reproduction or other evidence: with only the source change reverted and the new test applied, the test fails at the first cross-family pair — `AssertionError: setTimeout then clearImmediate: expected { timerId: 'timer:cancel:50:72', …(9) } to match object { kind: 'cancel', …(3) }`. With the change, all nine pairs match Node. - User-visible CLI/MCP output (if applicable): analyzing a file containing `clearImmediate(setTimeout(...))`, `clearTimeout(setTimeout(...))` and `clearInterval(setImmediate(...))` yields **1** resolved `cancels-timer` relation plus two `ambiguous-target` timer unknowns, where the same input previously yielded **3** resolved relations and no timer unknown. - Remaining proof gaps: Node versions other than v24.22.1 were not probed; the family split is documented behavior in `timers.html` and stable across supported versions. For boundary fixes: not applicable — no filesystem, process, or provider boundary is involved; this is a pure IR projection change covered by the domain tests above. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `node --import tsx` probe over 9 schedule/cancel pairs, Node v24.22.1 — setTimeout/setInterval cancel with clearTimeout or clearInterval; setImmediate cancels with clearImmediate; the other five keep running - `node node_modules/vitest/vitest.mjs run src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts` — 7 passed - `node node_modules/vitest/vitest.mjs run --project domain` — 8343 passed, 0 failed - `node node_modules/typescript/bin/tsc --noEmit -p .` — exit 0 - `npx oxfmt --check src/domain/javascript/javascriptSemanticAsyncEffects.ts src/domain/javascript/javascriptSemanticAnalysis.timers.test.ts` — clean - `node scripts/rea.mjs analyze-javascript-application <fixture> --format json` — 1 resolved `cancels-timer`, 2 `ambiguous-target` timer unknowns (previously 3 resolved, 0 unknowns) ## Compatibility, safety, and release - Breaking changes or migration steps: none. No public schema, tool contract, or CLI flag changes. A graph relation that was previously `resolved` for a cross-family clear is now `candidate` with a timer unknown — the correction callers want. - Real Hopper/Ghidra, browser, or OS coverage: not applicable. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none. Static analysis only; no target interaction. ## Author readiness Human owner: @XiaoBinGan - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-05 01:47:43 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7797
No description provided.