Skip to content

[BUG] httpx.Client() crashes when NO_PROXY contains bracketed IPv6 [::1] — Invalid port ':1]' #1541

Closed as not planned

Description

@FiretrUCK666

Bug Description

Constructing a default httpx.Client() (with trust_env=True) crashes at startup when the process environment contains a bracketed IPv6 loopback in NO_PROXY / no_proxy, e.g. localhost,127.0.0.1,::1,[::1]. The [::1] entry is treated as a URL, its :1] suffix is parsed as a port, and client construction raises instead of starting.

This bit us in the field: an MCP host process inherited that NO_PROXY value, so every httpx.Client() created by Basic Memory's dependency tree blew up with Invalid port: ':1]' (same signature as the FastEmbed Invalid port failure). Removing just the [::1] entry (keeping bare ::1) fixes it.

Steps To Reproduce

  1. Install version 0.23.2 (bundles httpx 0.28.1).
  2. Set the environment (Windows 11, but the parsing bug is platform-independent):
    • NO_PROXY=localhost,127.0.0.1,::1,[::1]
    • no_proxy=localhost,127.0.0.1,::1,[::1]
  3. Run:
    import httpx
    c = httpx.Client()
  4. See error (reproduced with the venv interpreter shipped by uvx --prerelease=allow basic-memory mcp).

Control case: scrubbing the env in-process to NO_PROXY=localhost,127.0.0.1,::1 (bare IPv6, no brackets) plus HTTP_PROXY/HTTPS_PROXY/ALL_PROXY=http://127.0.0.1:7078 constructs the client fine.

Expected Behavior

A bracketed IPv6 loopback in NO_PROXY should be accepted (it is the standard URL-form of ::1, and tools like curl accept it), or at worst that single entry should be skipped with a warning. Client construction must not raise for a bypass-list entry.

Actual Behavior

httpx.Client() raises during __init__:

ValueError: invalid literal for int() with base 10: ':1]'
...
  File "httpx/_client.py", line 698, in __init__
    URLPattern(key): None
  File "httpx/_utils.py", line 172, in __init__
    url = URL(pattern)
  File "httpx/_urls.py", line 117, in __init__
    self._uri_reference = urlparse(url, **kwargs)
  File "httpx/_urlparse.py", line 321, in urlparse
    parsed_port: int | None = normalize_port(port, scheme)
  File "httpx/_urlparse.py", line 411, in normalize_port
    raise InvalidURL(f"Invalid port: {port!r}")
httpx.InvalidURL: Invalid port: ':1]'

So any code path that builds a default client while that NO_PROXY value is present fails before doing any work.

Environment

  • OS: Windows 11 (10.0.26200)
  • Python version: 3.12.12 (venv created by uv)
  • Basic Memory version: 0.23.2
  • Installation method: uvx --prerelease=allow basic-memory mcp (uvx 0.11.3)
  • Bundled httpx version: 0.28.1

Additional Context

  • Triggering value (process env): NO_PROXY / no_proxy = localhost,127.0.0.1,::1,[::1]. The [::1] entry alone (NO_PROXY=[::1]) is sufficient to reproduce.
  • Safe value verified: localhost,127.0.0.1,::1 (bare ::1, no brackets) works, including with the full proxy set (HTTP_PROXY/HTTPS_PROXY/ALL_PROXY=http://127.0.0.1:port).
  • Searched existing issues for NO_PROXY / brackets / httpx / invalid port and found no duplicate.
  • The crash happens inside httpx (_utils.URLPattern <- _client proxy-map build from get_environment_proxies), surfaced through Basic Memory because it is the shipped dependency. If httpx is vendored/pinned here, the fix belongs here; otherwise forwarding the minimal repro upstream to httpx would resolve it.

Possible Solution

Options, cheapest first:

  1. Sanitize NO_PROXY/no_proxy entries before they reach URLPattern: strip one pair of surrounding brackets from bracketed IPv6 literals ([::1] -> ::1), consistent with URL host parsing.
  2. Alternatively, catch InvalidURL per bypass entry and skip/warn instead of failing client construction.
  3. Alternatively, construct internal clients with trust_env=False (or explicit proxy=/no_proxy=) on paths that must never be affected by ambient proxy env.
  4. If the vendored httpx is old, pick up the upstream fix if one exists for bracketed NO_PROXY hosts.

Workaround for affected users: drop [::1] from NO_PROXY/no_proxy, keeping localhost,127.0.0.1,::1.

Activity

  1. added this to the v0.24.0 milestone on Sep 14, 2026
  2. phernandez commented on Sep 14, 2026

    @phernandez
    Member

    Investigated against fetched origin/main at dff6656d94e5bd10a06617d70c2ec92403ca8f4a in the existing reusable worktree, on fresh branch codex/fix-1541-ipv6-proxy.

    Disposition: blocked pending upstream fix / explicit product proxy-policy decision; no Core patch proposed.

    Reproduction with the repository environment

    uv run --locked python resolves httpx 0.28.1 (Python 3.14 in this worktree). No network request is needed. Child processes received explicit test environments; the parent environment and user configuration were not modified.

    NO_PROXY / no_proxy value httpx.Client httpx.AsyncClient Core _build_asgi_client
    [::1] InvalidURL: Invalid port: ':1]' same constructs successfully
    localhost,127.0.0.1,::1,[::1] same failure same failure constructs successfully
    localhost,127.0.0.1,::1 constructs successfully constructs successfully constructs successfully

    These cases included HTTP_PROXY/HTTPS_PROXY/ALL_PROXY pointing at http://127.0.0.1:7078. Separate uppercase-only and lowercase-only bypass-variable cases also reproduce through both Core's _default_http_client(30) and Hugging Face's default_client_factory() (resolved huggingface-hub 1.23.0).

    Minimal command for upstream reproduction, scoped to a child process:

    env NO_PROXY='[::1]' no_proxy='[::1]' HTTP_PROXY=http://127.0.0.1:7078 \
      uv run --locked python -c 'import httpx; print(httpx.__version__); httpx.Client()'

    Root cause: httpx._utils.get_environment_proxies() classifies [::1] as a hostname and emits all://*[::1]; URLPattern then fails parsing its port. Bare ::1 instead takes the IPv6 branch.

    Upstream status

    • Latest published release is still 0.28.1, also confirmed by PyPI. Core declares httpx>=0.28.0 and locks 0.28.1; this is an ordinary dependency, not vendored code. There is no newer stable release to adopt.
    • Current upstream parser retains the same hostname/IPv6 dispatch.
    • Upstream PR #3741 is open, unmerged, and addresses IPv6 CIDR, not a released bracketed-literal fix. It should not be treated as resolving this issue.

    Complete Core httpx construction inventory and callers

    There are five direct construction sites in src/basic_memory:

    1. mcp/async_client.py:66, _build_asgi_client: explicit ASGITransport. httpx uses allow_env_proxies = trust_env and transport is None, so this client already skips environment proxy parsing and is unaffected. Adding trust_env=False here would not fix the reported failure.
    2. mcp/async_client.py:302, _cloud_client: network client for cloud project/proxy requests, reached through routing helpers and MCP/CLI typed clients.
    3. mcp/async_client.py:322, get_cloud_control_plane_client: network client for control-plane endpoints, also used by WebDAV push/pull transfer factories.
    4. cli/auth.py:52, CLIAuth._get_http_client: authentication network client, unless a caller injects its own factory.
    5. cli/commands/cloud/api_client.py:78, _default_http_client: generic cloud API requests, unless a caller injects its own factory.

    The typed MCP clients consume existing clients; they do not construct additional httpx transports. Upload uses get_client or its supplied factory; WebDAV transfer uses the control-plane factory or its supplied factory. The global Core client-factory hook delegates ownership to the embedding host.

    Dependency-owned paths also matter: Core's FastEmbed embedding and rerank providers construct TextEmbedding / TextCrossEncoder; FastEmbed model management calls Hugging Face snapshot_download, whose HTTP factory constructs its own default httpx.Client. That factory independently reproduces the crash. The OpenAI embedding provider constructs AsyncOpenAI without an injected HTTP client; LiteLLM delegates to aembedding, likewise outside the Core routing factory. Searching the integration/plugin/script sources found no additional direct Python httpx client construction sites.

    Why no narrow Core fix

    Disabling environment handling on network clients would change user proxy routing and environment certificate behavior. Normalizing or dropping process-global proxy variables would affect the host and all concurrent dependency clients. A wrapper around only the four affected Core-owned network clients would leave the demonstrated Hugging Face/FastEmbed failure intact. Reimplementing environment proxy discovery/mount matching or replacing dependency-global HTTP factories would require a wider, explicit product contract and maintenance commitment.

    The preferred resolution is upstream parsing support followed by a released dependency update. If Core must ship a workaround first, maintainers need to decide the supported proxy policy and dependency-client coverage explicitly. The existing user-controlled workaround is to replace the bracketed loopback entry with bare ::1, retaining other bypass entries and proxy settings.

    No source or lockfile changes, no PR, and no merge. Focused executable reproductions were run; fast-check/doctor were not run because no Core change was made. Worktree remains clean; dirty main checkout and existing topology were preserved.

  3. phernandez commented on Sep 14, 2026

    @phernandez
    Member

    Milestone status (2026-09-14): reproduced and understood; blocked on upstream or an explicit proxy-policy decision.

    Fresh upstream read-back is unchanged: HTTPX's latest release is still 0.28.1, and encode/httpx#3741 remains open and addresses IPv6 CIDR rather than a released bracketed-literal fix. Core's local ASGI client is unaffected, but Core-owned network clients and dependency-owned Hugging Face/FastEmbed clients can still fail from NO_PROXY=[::1].

    There is no narrow Core-only patch that fixes every affected client without changing ambient proxy semantics. Current workaround remains: use bare ::1 instead of [::1]. Next step is either adopt an upstream release that fixes bracketed literals or make an explicit product decision about sanitizing process proxy configuration and dependency-client coverage. No PR is pending.

  4. FiretrUCK666 commented on Sep 14, 2026

    @FiretrUCK666
    Author

    Follow-up: the bracketed entry is injected by the host harness at every launch, so the "drop [::1]" workaround does not hold in such hosts.

    Traced the source of the reported NO_PROXY value on my machine. It is not persisted anywhere — it is written into the environment at runtime, by the host process that launches the app, so editing the variable by hand does not survive a restart:

    • The host is DeepSeek Harness (@deepseek-ai/dsh-http-proxy 0.1.5-rc.1). Its LOOPBACK_NO_PROXY constant is ["localhost","127.0.0.1","::1","[::1]"] (lib/index.js:19-24); withLoopback() merges it into the proxy policy and applyPolicyEnv() writes the result back into process.env.no_proxy / NO_PROXY (lines 334-347, 364-375).
    • Every child process launched by that host (including the stdio MCP server built on Basic Memory) therefore inherits localhost,127.0.0.1,::1,[::1], and the bracket makes httpx 0.28.1 raise InvalidURL: Invalid port: ':1]' before any work happens.
    • The persistence-side value in the registry is clean (localhost,127.0.0.1,::1); the bracketed form exists only in the process environment, and clearing it manually is undone on the next launch. That is why the documented workaround cannot be applied by affected users in this setup.

    The bracket was added for undici, whose older releases parsed a bare ::1 as host : port 1. That is no longer true for current undici (8.10.2 handles bare IPv6 explicitly in EnvHttpProxyAgent, lib/dispatcher/env-http-proxy-agent.js:120-139), so the injecting side can drop it — I am filing this with the host project and will note the outcome here. If it helps, this is a concrete "bracketed literal" case file for the upstream httpx parsing fix you are waiting on; nothing is required from Basic Memory in the meantime, and the local workaround (clean NO_PROXY for the MCP child process) is holding.

  5. removed this from the v0.24.0 milestone on Sep 14, 2026
  6. phernandez commented on Sep 14, 2026

    @phernandez
    Member

    Closing as external/not planned and removing from the v0.24.0 milestone.

    The follow-up identified the root cause in the launching host: DeepSeek Harness's @deepseek-ai/dsh-http-proxy injects the redundant bracketed IPv6 entry [::1] into every child process alongside bare ::1. HTTPX 0.28.1 then fails while parsing that inherited NO_PROXY value. The reporter is taking the correction to the host, where the entry can be removed for every launched child.

    Basic Memory will not mutate process-global proxy variables or disable trust_env:

    • proxy and certificate policy belongs to the host/user environment;
    • trust_env=False would break legitimate proxy/CA configuration;
    • wrapping only Core-owned clients would leave dependency-owned HTTPX clients such as Hugging Face/FastEmbed exposed;
    • reimplementing HTTPX environment parsing in Basic Memory would create an incomplete parallel policy.

    The appropriate fixes are for the injecting host to stop adding [::1], and optionally for HTTPX upstream to tolerate the bracketed literal. No Core code change or release blocker remains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds reviewA human maintainer needs to look at this issue

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions