Skip to content

fix(javascript): keep timer cancellation within Node's handle family - #7263

Closed
XiaoBinGan wants to merge 1 commit into
morluto:mainfrom
XiaoBinGan:fix/timer-cancel-family
Closed

XiaoBinGan wants to merge 1 commit into
morluto:mainfrom
XiaoBinGan:fix/timer-cancel-family

Conversation

@XiaoBinGan

@XiaoBinGan XiaoBinGan commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

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 (fix(javascript): report fork's silent stdio as piped #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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-27T09:48:45.716180Z
We couldn’t complete this review. Comment @codex review to try again.
428548b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@XiaoBinGan XiaoBinGan changed the title test fix(javascript): keep timer cancellation within Node's handle family Oct 26, 2026
@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no blocking issues were found.

Summary

Timer cancellation now links only schedules in the same Node handle family.

  • Timer cancellations only link to schedules from the matching Node handle family.

No actionable issues were found. Tests were not run during this review. Whether another PR supersedes this one could not be confirmed because GitHub access was unavailable.

Reviews (5) · Last reviewed commit: "fix(javascript): keep timer cancellation..." · Reviewed by Greptile

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

Note on the two failing checks: neither is caused by this diff.

Static checks fails on main itself, before reaching anything in this PR. 662ec2e45 left scripts/verify-apk-signature.mjs unformatted, so oxfmt --check . exits 1 on a clean main checkout and names that file. #7262 is a one-file fix for exactly that and will clear this for every open PR.

Validate author declaration fails with a GitHub API error, not a declaration error:

RequestError [HttpError]: <!DOCTYPE html>
    at async openHeadsMatching (file:///home/runner/work/rea/rea/scripts/ci/pr-readiness.mjs:21:22)

openHeadsMatching pages the full open-PR list through GraphQL to prove live head coverage. With 547 open PRs that is six pages, and the call is currently returning an HTML error page instead of a GraphQL response. #7260 hit the identical failure nine minutes earlier. A retrigger through an empty commit reproduced it, so this is not a one-off.

The declaration in the description is complete (Human owner: @XiaoBinGan, three checked lines) and scripts/ci/pr-readiness.mjs reports success as the commit status on this head.

@XiaoBinGan
XiaoBinGan force-pushed the fix/timer-cancel-family branch from b4bd1da to 6b74150 Compare October 26, 2026 18:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b74150bf7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +343 to +345
timerHandleFamily(schedule.method) !==
timerHandleFamily(candidate.method)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Represent exact cross-family handles as known no-ops

When a cancellation handle has one fully resolved cross-family origin (for example the added const h = setTimeout(...); clearImmediate(h) case), filtering the origin here makes resolveTimers set resolution: "unresolved"; JavaScriptSemanticGraphAsyncProjection.ts:209-215 then emits an ambiguous-target unknown. The target and incompatibility are actually known, so this reports false uncertainty (including the misleading “handle resolution was unresolved” detail) instead of a known no-op; model or suppress that state without generating an unknown.

AGENTS.md reference: AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

The Test shard / Portable tests / CI required failures here are pre-existing on main, not caused by this one-file test addition.

Verified on a clean checkout of upstream/main with no local changes: tests/boundary/browser/sazWebCapture.test.ts fails two tests —

AssertionError: expected { provider_id: 'saz', …(4) } to not have property "partial_observation"

and src/application/SetupRegistrationPrewarm.test.ts fails settles the prewarm deadline after a detached descendant inherits the exited runner's pipes with expected '' to contain 'descendant:'. Both reproduce on main itself, and the same shards fail on four unrelated open PRs (#7267, #7261, #7260, #7258). Recent main CI runs are 14 failure / 25 cancelled, 0 success.

For this PR specifically: it adds one test file under tests/boundary/firmware/, which does not import any of the failing modules. Locally, firmwareStagingBudgetWiring.test.ts passes (2 tests), and the branch is rebased onto current main with behind=0.

Static checks was also failing on main earlier (scripts/verify-apk-signature.mjs formatting, since repaired upstream by 741f2de75), which is why earlier runs of this PR showed it.

@XiaoBinGan
XiaoBinGan force-pushed the fix/timer-cancel-family branch from cbdef36 to 3237898 Compare October 27, 2026 05:21
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

Node keeps timeouts and immediates in disjoint handle families. clearTimeout
and clearInterval are aliases that act only on a Timeout from setTimeout or
setInterval, while clearImmediate acts only on an Immediate from setImmediate.
Crossing the families returns without doing anything, so the scheduled
callback still runs.

REA linked a cancellation whenever the cleared binding traced back to any timer
schedule, so clearImmediate(setTimeout(work, 10)) reported a complete
cancellation and the graph emitted a resolved cancels-timer relation. It also
flagged no uncertainty for it.

Require the cancel and the schedule to share a handle family before linking
them. A cross-family clear now keeps linkedTimerId null and resolution
unresolved, and the graph reports the cancellation as a candidate with an
ambiguous-target unknown instead of a resolved fact.

Verified against Node v24.22.1 over all nine schedule/cancel pairs: setTimeout
and setInterval with clearTimeout or clearInterval cancel, and setImmediate
with clearImmediate cancels; the other five combinations leave the callback
running. Node's own lib/timers.js shows why: clearTimeout requires
timer?._onTimeout, clearImmediate requires immediate?._onImmediate and returns
early otherwise, and clearInterval delegates to clearTimeout.
@XiaoBinGan
XiaoBinGan force-pushed the fix/timer-cancel-family branch from 3237898 to 428548b Compare October 27, 2026 09:48
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@XiaoBinGan

Copy link
Copy Markdown
Contributor Author

Closing to free a review slot while main is red — not a change of mind about the fix.

This PR is source code, so its Source tests job inherits main's current failures. main still fails on two files unrelated to this change:

Until those land, every source PR's Source tests is red for reasons outside its diff, which makes review harder than it needs to be. CONTRIBUTING asks contributors to prioritise a focused repair of main before updating affected PRs, so I am parking this one rather than asking for review on inherited failures.

The fix and its red/green evidence are unchanged and the branch stays rebased:

  • branch fix/timer-cancel-family
  • test added by this PR fails on unmodified main and passes with the change

I will reopen it as soon as main is green again. Sorry for the churn — flagging the reasoning instead of leaving it unexplained.

@XiaoBinGan XiaoBinGan closed this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant