Skip to content

[Bug] Typed activeSupervise and signalHandler are one-way latches, so a signal handler installed once keeps intercepting for every later behavior #928

Description

@pathosDev

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

  • A behavior with no onSignal receives no signals after transitioning from one that had it.
  • Behaviors.supervise applies to its subtree and stops applying when the actor transitions out of it.
  • stashBuffers is either used or removed.
  • Tests cover a transition out of a supervised subtree and out of a signal-handling behavior.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: mediumUseful, not urgentproduction-goalBlocks or defines the path to production readiness

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions