#633·memU

[BUG] urllib HTTPS calls silently fail on Pythons with no CA bundle — events never delivered, spool grows forever

Author: nevamind-agentCreated Aug 9, 2026Updated Aug 10, 2026
Labelsbugarea/analytics

Description

On a Python that has no CA bundle configured, every urllib-based HTTPS call in the CLI fails TLS verification and is swallowed by a fail-open except. The user-visible result is total, silent loss: memu-<host> report flush prints delivered 0 event(s), .sending spool files accumulate forever, and nothing appears in the backend log — because the request never leaves the machine.

The trigger is a python.org framework build whose Install Certificates.command was never run:

$ python3 -c "import ssl; print(ssl.get_default_verify_paths())"
DefaultVerifyPaths(cafile=None, capath=None, ...)     # no trust store at all

certifi is installed in that same environment (it comes in as a transitive dep) — urllib just doesn't consult it. This is why the failure is so confusing to diagnose: httpx paths keep working. memu-<host> retrieve succeeds normally, because memu/cloud.py uses httpx, which defaults to certifi. Only the two urllib call sites break, and both of them are deliberately silent.

Affected call sites

  1. src/memu/events.py_post() — the except Exception: return RETRY leg. _flush() then stalls on the first event, retains the file, and breaks, so a machine in this state never delivers a single event and the spool grows until MAX_SPOOL_BYTES starts dropping.
  2. src/memu/hosts/templates.py_get()except Exception: return None, which every caller reads as "fall back to the bundled copy". So this machine also silently never receives a server-side template or doc refresh, with no signal of any kind.

Both swallows are correct per the fail-open contract; the gap is that a machine-wide misconfiguration is indistinguishable from the server is briefly unreachable, and the first one never resolves on its own.

Evidence

Reproduced and confirmed on a local -e install by instrumenting _post:

[memu.events] POST https://api.memu.so/api/memu/analytics/events — 5 spool file(s)
[memu.events]   -> 374B, auth=yes, ua=memu-cli/2.0.0b0
[memu.events]   <- URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED]
                   certificate verify failed: unable to get local issuer certificate (_ssl.c:1081)>
[memu.events] ...sending[0] cli_install_started -> retry
[memu.events] ...sending: stalled, retaining 1 event(s)
[memu.events] flush finished: 0 accepted, 0 rejected
delivered 0 event(s)

Re-running the identical command with nothing changed but the trust store drains the backlog completely:

$ SSL_CERT_FILE=$(python3 -c "import certifi;print(certifi.where())") memu-codex report flush
[memu.events]   <- HTTP 200 '{"status":"ok","message":"Event recorded","meta":{},"data":{"duplicate":false}}'
...
[memu.events] flush finished: 11 accepted, 0 rejected

Five .sending files had been sitting on this machine since the install — three days of cli_install_started, cli_install_succeeded, memory_search_succeeded and one cli_install_failed, none of which reached analytics.

Environment

macOS 15 (Darwin 25.6.0), arm64, python.org framework build CPython 3.14.3 (/Library/Frameworks/Python.framework/Versions/3.14), OpenSSL 3.0.18

Steps to reproduce

  1. Install memU on a python.org framework Python where /Applications/Python 3.x/Install Certificates.command has never been run (verify with python3 -c "import ssl;print(ssl.get_default_verify_paths().cafile)"None).
  2. Run any host install so events are spooled to ~/.memu/events.jsonl.
  3. Run memu-<host> report flush.
  4. Observe delivered 0 event(s), a ~/.memu/events.jsonl.<hex>.sending file left behind on every attempt, and no corresponding rows on the backend.

Homebrew and uv-managed interpreters are unaffected — they ship a working trust store — so this reproduces only on the python.org installer builds, which is exactly the population least likely to notice.

Expected behavior

A machine that can reach the API over httpx should also reach it over urllib. Concretely: report flush should deliver, and template/doc refresh should work, without the user having to discover SSL_CERT_FILE for themselves.

Version

memu-cli 2.0.0b0 (branch feat/config-env-command)

Severity

Major

Additional Information

Why this matters more than one user's laptop

Analytics is the thing that tells us whether an install worked. A cohort of users on python.org Pythons reports zero events — not a low number, zero — and the shape of that loss is invisible from the backend side: it looks identical to "those users never installed". Any funnel measured on cli_install_startedinstall_guide_openedcli_install_succeeded is currently undercounting by an unknown amount, biased toward one specific install method. ADR 0016's whole premise is that silent failure is what the event system exists to end; this is a silent failure of the event system.

Suggested direction (not a prescription)

  • Pin the trust store. certifi is already present in the dependency tree; both _post() and templates._get() could pass context=ssl.create_default_context(cafile=certifi.where()), guarded so a missing certifi degrades to today's behavior rather than raising. This makes urllib behave like the httpx paths that already work.
  • Make the failure visible when someone goes looking. A debug channel gated behind an env var (I used MEMU_EVENTS_DEBUG=1 locally, writing to stderr) turns this from an hours-long dig into one command. Happy to open a PR with that — it's how the diagnosis above was produced.
  • Worth deciding as one call, since templates._get() has the same hole and the same fix.

Related constants/ADRs: MAX_FLUSH_POSTS, MAX_SPOOL_BYTES in events.py; ADR 0016 (client event reporting).