Skip to content

fix: Only trigger cell when ALL its dependencies are unblocked#8374

Merged
akshayka merged 1 commit intomarimo-team:mainfrom
snooze92:eager_trigger
Feb 19, 2026
Merged

fix: Only trigger cell when ALL its dependencies are unblocked#8374
akshayka merged 1 commit intomarimo-team:mainfrom
snooze92:eager_trigger

Conversation

@snooze92
Copy link
Contributor

📝 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:

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

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.

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.
@snooze92 snooze92 requested a review from dmadisetti as a code owner February 19, 2026 16:48
@vercel
Copy link

vercel bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 19, 2026 4:49pm

Request Review

@snooze92
Copy link
Contributor Author

I don't seem able to add a label myself.

I see tests._server.api.endpoints.test_resume_session.test_resume_session_with_watch failed in the CI, but it works locally and I notice @pytest.mark.flaky(reruns=3) in the code for that test.

@dmadisetti dmadisetti requested a review from akshayka February 19, 2026 17:47
@dmadisetti dmadisetti added the bug Something isn't working label Feb 19, 2026
Copy link
Contributor

@akshayka akshayka left a comment

Choose a reason for hiding this comment

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

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.

@akshayka akshayka added the bash-focus Area to focus on during release bug bash label Feb 19, 2026
@akshayka akshayka merged commit 9de151c into marimo-team:main Feb 19, 2026
34 of 43 checks passed
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bash-focus Area to focus on during release bug bash bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants