fix: Only trigger cell when ALL its dependencies are unblocked#8374
Merged
akshayka merged 1 commit intomarimo-team:mainfrom Feb 19, 2026
Merged
fix: Only trigger cell when ALL its dependencies are unblocked#8374akshayka merged 1 commit intomarimo-team:mainfrom
akshayka merged 1 commit intomarimo-team:mainfrom
Conversation
I noticed that when one cell depended on two branches that stopped,
the dependant cell correctly stopped initially. But one of the two
branches getting "unstopped" unfortunately triggered the dependant
cell to run too soon, which could lead to issues such as NameError.
It seems that every new trigger uses clean state, where the whole
transitive closure of the triggered cell gets triggered without
the rest of the tree being evaluated at all. With this change, I
look for any cell that is about to be run for blocked missing refs
and I cancel the cell and its transitive closure whenever I find
such blocked missing refs.
Unfortunately, I have to assume that a cell found in "exception"
state with a None exception was a stop, and re-wrap the None in a
MarimoStopError to get the correct "ancestor stopped" message in
the frontend. That seems like a separate cleanup that would be good
for Marimo: either make MarimoStopError extend Exception rather
than BaseException, or let Cell.exception store BaseExceptions.
In CancelledCells, we might now call add with the same
`raising_cell` arguments but different `descendants`, in the
case where two different cells depend on the same blocked cell.
Things workout fine without a change, but it seems more correct
to append all descendants in this case for completeness.
Below is a simple notebook to manually reproduce/verify:
```python
import marimo
__generated_with = "0.19.11"
app = marimo.App(width="medium")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell
def _(mo):
a = mo.ui.text(value="ha!", label="A:").form()
a
return (a,)
@app.cell
def _(a, mo):
mo.stop(not a.value, "A not submitted")
aa = a.value.upper()
return (aa,)
@app.cell
def _(mo):
b = mo.ui.text(value="beh...", label="B:").form()
b
return (b,)
@app.cell
def _(b, mo):
mo.stop(not b.value, "B not submitted")
bb = b.value.upper()
return (bb,)
@app.cell
def _(aa, bb):
aa + bb
return
@app.cell
def _(aa, bb):
bb + aa
return
if __name__ == "__main__":
app.run()
```
Before this fix, submitting either form before the other runs the last
two cells and fails with a NameError because either `aa` or `bb` is
not defined...
A side effect of this fix is that when explicitly trying to run a cell
that depends on a stopped ancestor, instead of getting a NameError we
now get a clear "stopped ancestor" message.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Author
|
I don't seem able to add a label myself. I see |
akshayka
approved these changes
Feb 19, 2026
Contributor
akshayka
left a comment
There was a problem hiding this comment.
Thanks for this fix. It's a great quality of life improvement. We will release this under a new minor release, since it is a behavior change.
LiquidGunay
pushed a commit
to LiquidGunay/marimo
that referenced
this pull request
Feb 21, 2026
…o-team#8374) ## 📝 Summary I noticed that when one cell depended on two branches that stopped, the dependant cell correctly stopped initially. But one of the two branches getting "unstopped" unfortunately triggered the dependant cell to run too soon, which could lead to issues such as NameError. This is an attempt at fixing the issue. ## 🔍 Description of Changes It seems that every new trigger uses clean state, where the whole transitive closure of the triggered cell gets triggered without the rest of the tree being evaluated at all. With this change, I look for any cell that is about to be run for blocked missing refs and I cancel the cell and its transitive closure whenever I find such blocked missing refs. Unfortunately, I have to assume that a cell found in "exception" state with a None exception was a stop, and re-wrap the None in a MarimoStopError to get the correct "ancestor stopped" message in the frontend. That seems like a separate cleanup that would be good for Marimo: either make MarimoStopError extend Exception rather than BaseException, or let Cell.exception store BaseExceptions. In CancelledCells, we might now call add with the same `raising_cell` arguments but different `descendants`, in the case where two different cells depend on the same blocked cell. Things workout fine without a change, but it seems more correct to append all descendants in this case for completeness. Below is a simple notebook to manually reproduce/verify: ```python import marimo __generated_with = "0.19.11" app = marimo.App(width="medium") @app.cell def _(): import marimo as mo return (mo,) @app.cell def _(mo): a = mo.ui.text(value="ha!", label="A:").form() a return (a,) @app.cell def _(a, mo): mo.stop(not a.value, "A not submitted") aa = a.value.upper() return (aa,) @app.cell def _(mo): b = mo.ui.text(value="beh...", label="B:").form() b return (b,) @app.cell def _(b, mo): mo.stop(not b.value, "B not submitted") bb = b.value.upper() return (bb,) @app.cell def _(aa, bb): aa + bb return @app.cell def _(aa, bb): bb + aa return if __name__ == "__main__": app.run() ``` Before this fix, submitting either form before the other runs the last two cells and fails with a NameError because either `aa` or `bb` is not defined... A side effect of this fix is that when explicitly trying to run a cell that depends on a stopped ancestor, instead of getting a NameError we now get a clear "stopped ancestor" message. ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [x] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] Tests have been added for the changes made. - [x] Documentation has been updated where applicable, including docstrings for API changes. - [x] Pull request title is a good summary of the changes - it will be used in the [release notes](https://github.com/marimo-team/marimo/releases).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Summary
I noticed that when one cell depended on two branches that stopped, the dependant cell correctly stopped initially. But one of the two branches getting "unstopped" unfortunately triggered the dependant cell to run too soon, which could lead to issues such as NameError.
This is an attempt at fixing the issue.
🔍 Description of Changes
It seems that every new trigger uses clean state, where the whole transitive closure of the triggered cell gets triggered without the rest of the tree being evaluated at all. With this change, I look for any cell that is about to be run for blocked missing refs and I cancel the cell and its transitive closure whenever I find such blocked missing refs.
Unfortunately, I have to assume that a cell found in "exception" state with a None exception was a stop, and re-wrap the None in a MarimoStopError to get the correct "ancestor stopped" message in the frontend. That seems like a separate cleanup that would be good for Marimo: either make MarimoStopError extend Exception rather than BaseException, or let Cell.exception store BaseExceptions.
In CancelledCells, we might now call add with the same
raising_cellarguments but differentdescendants, in the case where two different cells depend on the same blocked cell. Things workout fine without a change, but it seems more correct to append all descendants in this case for completeness.Below is a simple notebook to manually reproduce/verify:
Before this fix, submitting either form before the other runs the last two cells and fails with a NameError because either
aaorbbis not defined...A side effect of this fix is that when explicitly trying to run a cell that depends on a stopped ancestor, instead of getting a NameError we now get a clear "stopped ancestor" message.
📋 Checklist