Problem
In the typed behavior DSL, activeSupervise and signalHandler are one-way latches. Both are written when a behavior node declares them and neither is ever cleared, so a supervision strategy or signal handler installed once keeps intercepting for every behavior the actor transitions into afterwards — including behaviors that deliberately declare none.
Behaviors.receive(...) without an onSignal is a statement that this behavior handles no signals. Today it silently inherits whatever the last behavior installed.
Evidence
src/typed/TypedActor.ts:203-210
.with({ kind: 'supervise' }, (n): ResolveStep => {
this.activeSupervise = n;
return { step: 'continue', next: n.child };
})
.with({ kind: 'receive' }, (n): ResolveStep => {
if (n.onSignal) this.signalHandler = n.onSignal;
return { step: 'done', final: n };
})
Both assignments are unconditional writes with no corresponding reset anywhere in the resolve path — a behavior that declares no onSignal leaves the previous one installed, and a supervise node is never popped when its subtree is left.
Note also stashBuffers two lines above: it is pushed to on every with-stash node and read nowhere, so it grows for the actor's lifetime.
Proposal
Resolve both from the behavior currently being entered rather than accumulating them on the actor: clear activeSupervise and signalHandler at the start of each resolve, and set them only from the nodes on the path to the behavior being adopted. That makes "no onSignal" mean what it says, and makes Behaviors.supervise scope to its subtree.
Related: #638 covers Behaviors.supervise ignoring the restart budget and breaking under nesting — same method, adjacent cause; fixing the scoping is a prerequisite for fixing the nesting.
Acceptance sketch
Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (v0.13.0) and re-verified before filing: confirmed by reading the resolve loop at the cited lines and confirming no reset exists on that path.
Part of the production-readiness review batch — tracked in #913.
Problem
In the typed behavior DSL,
activeSuperviseandsignalHandlerare one-way latches. Both are written when a behavior node declares them and neither is ever cleared, so a supervision strategy or signal handler installed once keeps intercepting for every behavior the actor transitions into afterwards — including behaviors that deliberately declare none.Behaviors.receive(...)without anonSignalis a statement that this behavior handles no signals. Today it silently inherits whatever the last behavior installed.Evidence
Both assignments are unconditional writes with no corresponding reset anywhere in the resolve path — a behavior that declares no
onSignalleaves the previous one installed, and asupervisenode is never popped when its subtree is left.Note also
stashBufferstwo lines above: it is pushed to on everywith-stashnode and read nowhere, so it grows for the actor's lifetime.Proposal
Resolve both from the behavior currently being entered rather than accumulating them on the actor: clear
activeSuperviseandsignalHandlerat the start of each resolve, and set them only from the nodes on the path to the behavior being adopted. That makes "noonSignal" mean what it says, and makesBehaviors.supervisescope to its subtree.Related: #638 covers
Behaviors.superviseignoring the restart budget and breaking under nesting — same method, adjacent cause; fixing the scoping is a prerequisite for fixing the nesting.Acceptance sketch
onSignalreceives no signals after transitioning from one that had it.Behaviors.superviseapplies to its subtree and stops applying when the actor transitions out of it.stashBuffersis either used or removed.Verification status
Found in the ten-lens production-readiness review of 2026-08-05 (
v0.13.0) and re-verified before filing: confirmed by reading the resolve loop at the cited lines and confirming no reset exists on that path.Part of the production-readiness review batch — tracked in #913.