[BUG] urllib HTTPS calls silently fail on Pythons with no CA bundle — events never delivered, spool grows forever
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 allcertifi 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
src/memu/events.py→_post()— theexcept Exception: return RETRYleg._flush()then stalls on the first event, retains the file, andbreaks, so a machine in this state never delivers a single event and the spool grows untilMAX_SPOOL_BYTESstarts dropping.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 rejectedFive .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
- Install memU on a python.org framework Python where
/Applications/Python 3.x/Install Certificates.commandhas never been run (verify withpython3 -c "import ssl;print(ssl.get_default_verify_paths().cafile)"→None). - Run any host install so events are spooled to
~/.memu/events.jsonl. - Run
memu-<host> report flush. - Observe
delivered 0 event(s), a~/.memu/events.jsonl.<hex>.sendingfile 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_started → install_guide_opened → cli_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.
certifiis already present in the dependency tree; both_post()andtemplates._get()could passcontext=ssl.create_default_context(cafile=certifi.where()), guarded so a missingcertifidegrades to today's behavior rather than raising. This makesurllibbehave like thehttpxpaths that already work. - Make the failure visible when someone goes looking. A debug channel gated behind an env var (I used
MEMU_EVENTS_DEBUG=1locally, 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).
Source: NevaMind-AI/memU