{"record":{"id":"2fd8847d96b0906b","repo":"Textualize/textual","slug":"node-must-be-running-to-subscribe-to-a-signal-has","errorCode":null,"errorMessage":"Node must be running to subscribe to a signal (has {node} been mounted)?","messagePattern":"Node must be running to subscribe to a signal \\(has (.+?) been mounted\\)\\?","errorType":"exception","errorClass":"SignalError","httpStatus":null,"severity":"error","filePath":"src/textual/signal.py","lineNumber":78,"sourceCode":"        node: DOMNode,\n        callback: SignalCallbackType[SignalT],\n        immediate: bool = False,\n    ) -> None:\n        \"\"\"Subscribe a node to this signal.\n\n        When the signal is published, the callback will be invoked.\n\n        Args:\n            node: Node to subscribe.\n            callback: A callback function which takes a single argument and returns anything (return type ignored).\n            immediate: Invoke the callback immediately on publish if `True`, otherwise post it to the DOM node to be\n                called once existing messages have been processed.\n\n        Raises:\n            SignalError: Raised when subscribing a non-mounted widget.\n        \"\"\"\n        if not node.is_running:\n            raise SignalError(\n                f\"Node must be running to subscribe to a signal (has {node} been mounted)?\"\n            )\n\n        if immediate:\n\n            def signal_callback(data: SignalT) -> None:\n                \"\"\"Invoke the callback immediately.\"\"\"\n                callback(data)\n\n        else:\n\n            def signal_callback(data: SignalT) -> None:\n                \"\"\"Post the callback to the node, to call at the next opertunity.\"\"\"\n                node.call_next(callback, data)\n\n        callbacks = self._subscriptions.setdefault(node, [])\n        callbacks.append(signal_callback)\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/signal.py#L60-L96","documentation":"Textual's Signal system only allows live, mounted nodes to subscribe because delivery requires an active message pump. Subscribing a widget before it is running raises SignalError.","triggerScenarios":"Calling signal.subscribe(node) in __init__, on the class, or in a handler that runs before mount; also subscribing an already-unmounted node.","commonSituations":"Wiring subscriptions in a widget constructor instead of on_mount; subscribing widgets created but never mounted; subscribing after removal from the DOM.","solutions":["Move subscribe() calls into on_mount (or the _on_mount handler)","If subscribing another node, verify node.is_running first","Unsubscribe in on_unmount to avoid leaks"],"exampleFix":"# before\nclass MyWidget(Widget):\n    def __init__(self):\n        super().__init__()\n        MY_SIGNAL.subscribe(self, self.on_signal)  # not mounted yet\n# after\nclass MyWidget(Widget):\n    def on_mount(self) -> None:\n        MY_SIGNAL.subscribe(self, self.on_signal)","handlingStrategy":"validation","validationCode":"if node.is_running:\n    SIGNAL.subscribe(node, callback)","typeGuard":null,"tryCatchPattern":"from textual.signal import SignalError\ntry:\n    SIGNAL.subscribe(self, self.on_signal)\nexcept SignalError:\n    self.call_after_refresh(lambda: SIGNAL.subscribe(self, self.on_signal))","preventionTips":["Subscribe only in on_mount","Unsubscribe in on_unmount"],"tags":["signal","pubsub","mount-lifecycle","textual"],"backgroundTag":"subscribe-before-mount","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}