Repository: mlflow/mlflow
Issue category: Bug Report

Issue:
[BUG] end_run() within nested run context affects parent run as well
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Where did you encounter this bug?

Local machine

### MLflow version

- Client: 2.15.5
- Tracking server: 2.12.2


### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 22.04
- **Python version**: 3.10



### Describe the problem

Executing this code terminates both the nested and parent run, but from the documentation it should terminate only the nested one.

```
import mlflow

mlflow.set_experiment("nested_run_debugging")
with mlflow.start_run():
    for i in range(10):
        with mlflow.start_run(nested=True):
            mlflow.log_param("index", i)
            if i == 3:
                mlflow.end_run("FAILED")
```

![image](https://github.com/user-attachments/assets/aa0d0dce-60a7-4671-93e6-4e38b9a4761e)


### Tracking information

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```shell
REPLACE_ME
```


### Code to reproduce issue

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->

```
import mlflow

mlflow.set_experiment("nested_run_debugging")
with mlflow.start_run():
    for i in range(10):
        with mlflow.start_run(nested=True):
            mlflow.log_param("index", i)
            if i == 3:
                mlflow.end_run("FAILED")
```


### Stack trace

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
REPLACE_ME
```


### Other info / logs

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
REPLACE_ME
```


### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/deployments`: MLflow Deployments client APIs, server, and third-party Deployments integrations
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [X] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations
[BUG] end_run() within nested run context affects parent run as well
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Where did you encounter this bug?

Local machine

### MLflow version

- Client: 2.15.5
- Tracking server: 2.12.2


### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 22.04
- **Python version**: 3.10



### Describe the problem

Executing this code terminates both the nested and parent run, but from the documentation it should terminate only the nested one.

```
import mlflow

mlflow.set_experiment("nested_run_debugging")
with mlflow.start_run():
    for i in range(10):
        with mlflow.start_run(nested=True):
            mlflow.log_param("index", i)
            if i == 3:
                mlflow.end_run("FAILED")
```

![image](https://github.com/user-attachments/assets/aa0d0dce-60a7-4671-93e6-4e38b9a4761e)


### Tracking information

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```shell
REPLACE_ME
```


### Code to reproduce issue

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->

```
import mlflow

mlflow.set_experiment("nested_run_debugging")
with mlflow.start_run():
    for i in range(10):
        with mlflow.start_run(nested=True):
            mlflow.log_param("index", i)
            if i == 3:
                mlflow.end_run("FAILED")
```


### Stack trace

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
REPLACE_ME
```


### Other info / logs

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
REPLACE_ME
```


### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/deployments`: MLflow Deployments client APIs, server, and third-party Deployments integrations
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [X] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations


Hints:
no.

`with mlflow.start_run(nested=True):`

means when the code exit the `with` block, the started run is ended.


if you use `mlflow.start_run(...)` without `with`, then it will not be ended automatically.
oh , I got your point, 

in your case, it unexpectedly terminates the parent run at the step 3 in the loop, then it leads to all subsequential nested runs become top-level runs.
I will file a fix later.
no.

`with mlflow.start_run(nested=True):`

means when the code exit the `with` block, the started run is ended.


if you use `mlflow.start_run(...)` without `with`, then it will not be ended automatically.
oh , I got your point, 

in your case, it unexpectedly terminates the parent run at the step 3 in the loop, then it leads to all subsequential nested runs become top-level runs.
I will file a fix later.

Patch:
diff --git a/mlflow/tracking/fluent.py b/mlflow/tracking/fluent.py
index 14c98dff0e9b6..6ffa788c34534 100644
--- a/mlflow/tracking/fluent.py
+++ b/mlflow/tracking/fluent.py
@@ -219,8 +219,10 @@ def __enter__(self):
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
-        status = RunStatus.FINISHED if exc_type is None else RunStatus.FAILED
-        end_run(RunStatus.to_string(status))
+        active_run_stack = _active_run_stack.get()
+        if self in active_run_stack:  # Is this run still active?
+            status = RunStatus.FINISHED if exc_type is None else RunStatus.FAILED
+            end_run(RunStatus.to_string(status))
         return exc_type is None
 
 


Test patch:
diff --git a/tests/tracking/fluent/test_fluent.py b/tests/tracking/fluent/test_fluent.py
index 486f78665e84b..77e6ed61bfd73 100644
--- a/tests/tracking/fluent/test_fluent.py
+++ b/tests/tracking/fluent/test_fluent.py
@@ -1659,3 +1659,21 @@ def test_subprocess_inherit_registry_uri(tmp_path):
         text=True,
     )
     assert sqlite_uri in stdout
+
+
+def test_end_run_inside_start_run_context_manager():
+    client = MlflowClient()
+
+    with mlflow.start_run() as parent_run:
+        with mlflow.start_run(nested=True) as child_run:
+            mlflow.end_run("FAILED")
+            assert client.get_run(child_run.info.run_id).info.status == RunStatus.to_string(
+                RunStatus.FAILED
+            )
+
+        assert client.get_run(parent_run.info.run_id).info.status == RunStatus.to_string(
+            RunStatus.RUNNING
+        )
+    assert client.get_run(parent_run.info.run_id).info.status == RunStatus.to_string(
+        RunStatus.FINISHED
+    )