fix(e2e/claude_code): unblock stage collection and align proxy env names with the rest of tests/e2e - #33426
Conversation
…ts/e2e Every claude_code compat cell used to read its own `LITELLM_PROXY_BASE_URL` and `LITELLM_PROXY_API_KEY` and duplicate the same 12-line "missing env, hard fail" block. The rest of `tests/e2e/` reads `LITELLM_PROXY_URL` and `LITELLM_MASTER_KEY` from `e2e_config.py`, so anyone standing up a live proxy for one suite had to export a second spelling for claude_code, and every cell repeated the same boilerplate. Centralize the resolution in `claude_code/_env.py`. `resolve_proxy()` prefers the suite-wide `LITELLM_PROXY_URL` / `LITELLM_MASTER_KEY` names and falls back to the legacy pair so existing CI wiring on stage keeps working during the roll-out. `require_proxy(compat_result)` is the one-liner cells call to bind `(base_url, api_key)` or hard-fail with a message that names both spellings. 55 cell files, `_basic_messaging.py`, and the driver's own unit-test fixture now go through the helper. `run_compat.sh` accepts either spelling and normalizes to the primary names before invoking pytest. `cron_vm/run_daily.sh` exports the primary names when launching pytest. `_pr_gate_unit_tests/test_env_resolution.py` pins the resolution rules so a future edit cannot silently reintroduce the drift: primary names win on tie, legacy names still resolve when primary is unset, mixed URL-primary key-legacy still resolves, empty-string exports are treated as unset, `require_proxy` names both spellings in its error message. Net diff: 71 files, +370/-1240.
…collects `test_bash_tool_restrictions.py` derived `REPO_ROOT = Path(__file__).resolve().parents[4]` and then joined `tests/e2e/claude_code/<feature>`. That works locally, but the stage container mounts tests/e2e/ at /app/e2e/, so parents[4] resolves to filesystem root and the `_bash_cells()` assertion looks for `/tests/e2e/claude_code/tool_use` — a path that doesn't exist. Collection interrupts before any test runs, so the entire e2e suite appears broken. Fix: `CLAUDE_CODE_DIR = Path(__file__).resolve().parents[1]` resolves to the sibling `claude_code/` dir in either layout, and the `relative_to(REPO_ROOT)` calls become `relative_to(CLAUDE_CODE_DIR)` so test IDs and error messages read the same. Adds `test_claude_code_dir_anchor_is_layout_independent` as a regression pin: it checks the anchor lands on a directory named `claude_code` that contains this test file, which would fail under the old parents[4] anchor when run from /app/e2e/.
Greptile SummaryThis PR fixes a pytest collection blocker in the stage container and consolidates the duplicated proxy-env boilerplate spread across 55+
Confidence Score: 5/5Safe to merge — all changes are confined to tests/e2e/ and fix a real collection blocker without touching production code Both changes are well-scoped: the path anchor fix is mechanically straightforward and backed by a new regression test that would have caught the original breakage, and the env-name refactor is thoroughly tested with 11 targeted unit tests covering every edge case. No production paths are touched and backward compatibility is preserved. No files require special attention; the key files to read are _env.py for the resolution logic and test_bash_tool_restrictions.py for the anchor fix.
|
| Filename | Overview |
|---|---|
| tests/e2e/claude_code/_env.py | New module centralizing proxy env resolution; clean fallback logic with or-chain; well-tested edge cases including empty-string treatment |
| tests/e2e/claude_code/_pr_gate_unit_tests/test_bash_tool_restrictions.py | Fixes the collection blocker by anchoring CLAUDE_CODE_DIR at parents[1] instead of the fragile REPO_ROOT parents[4]; adds layout-independent regression pin |
| tests/e2e/claude_code/_pr_gate_unit_tests/test_env_resolution.py | New regression suite (11 tests) pinning resolution rules; covers primary-wins-on-tie, legacy fallback, empty-string-as-unset, and error message content |
| tests/e2e/claude_code/run_compat.sh | Accepts either env spelling via shell parameter expansion, normalizes to primary names before export, updates error message and comment accordingly |
| tests/e2e/claude_code/cron_vm/run_daily.sh | Updates exported env var names from legacy pair to primary pair; comment updated to match |
| tests/e2e/claude_code/_basic_messaging.py | Removes 12-line boilerplate env check in favour of require_proxy(compat_result); correctly destructures the returned NamedTuple |
| tests/e2e/claude_code/_driver_unit_tests/test_basic_messaging.py | Updates the autouse fixture to set primary env names instead of legacy ones, keeping test behaviour aligned with the new resolution logic |
Reviews (1): Last reviewed commit: "fix(e2e): anchor claude_code Bash pin at..." | Re-trigger Greptile
…m a session fixture Every compat cell hardcodes a virtual model name like `claude-sonnet-4-6` or `claude-sonnet-4-6-bedrock-invoke` and hits the proxy expecting it to be routable. On stage those live in the deployed model_list; locally the `docker-config.yaml` under tests/e2e/ only declares one of them, so anything past haiku 400s with `Invalid model name`. `claude_code/test_config.yaml` is the ground-truth compat matrix config the deployment already uses. `_compat_models.py` loads it, normalizes the yaml keys pydantic would silently drop (vertex_ai_* → vertex_*), and selects the subset whose provider credentials are present in the environment. An autouse session fixture in `conftest.py` POSTs each selected deployment to `/model/new`, blocks until it is servable on the data plane, and tears them all down on session exit. Skips silently when the proxy env is unset so pure-unit runs stay hermetic. `test_compat_models.py` pins the invariants that keep this safe. Every cell-referenced name must have a yaml entry (drift check catches a cell probing a name the fixture never registered); the yaml has no unused declarations; the fixture registers exactly 15 deployments (3 tiers × 5 provider surfaces); vertex_ai_* yaml keys populate the pydantic body's vertex_* fields (they got silently dropped historically); Azure needs both AZURE_FOUNDRY_* env vars; Bedrock lifts creds from the ambient AWS chain; Vertex needs both the yaml refs AND ambient GCP credentials.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
`require_proxy` and `_basic_messaging.run_basic_messaging_cell` now take the env mapping (and the CLI runner) as constructor-style arguments with `os.environ` and `run_claude_models_parallel` as defaults. Tests exercise the branching by passing dicts and callables directly, so `monkeypatch.setenv` and `monkeypatch.setattr(_basic_messaging, "run_claude_models_parallel", ...)` are gone from every unit test in this refactor's blast radius.
`test_env_resolution.py` drops the `monkeypatch.setenv`/`delenv` fixtures and passes `env={...}` dicts to `require_proxy`. Added a new pinned check that a successful resolution leaves `compat_result` untouched, and split the "unset env" test into three explicit shapes (empty, primary-only, legacy-only) so a regression that swaps the precedence rule can no longer hide behind a single monkeypatched fixture.
`test_basic_messaging.py` (driver) replaces the `_install_fake_runner(monkeypatch, ...)` helper with `_make_fake_runner(...)` that returns a `(callable, captured_dict)` pair the test passes in via the helper's new `runner=` kwarg. Also drops the autouse `_proxy_env` fixture in favor of a module-level `_PROXY_ENV` dict each test wires through the helper's new `env=` kwarg. Added a regression pin that a missing-env call hard-fails without ever invoking the runner (so the guard order stays correct).
`test_run_daily_pytest_scrubs_env.py` updates its pin to assert the new suite-wide env spellings (`LITELLM_PROXY_URL` / `LITELLM_MASTER_KEY`) instead of the legacy `LITELLM_PROXY_BASE_URL` / `LITELLM_PROXY_API_KEY` that `run_daily.sh` used to export.
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 2 · PR risk: 0/10 |
`deployments_for_available_providers` used to read the yaml twice — once via `_raw_params_by_name` (for the availability check) and once via `load_all_deployments` (for the built pydantic bodies). Small file, only at session startup, but if the config were replaced mid-setup the two reads could see different views and hand back a mismatched result. Also unnecessary I/O. Consolidated to a single `_parse_entries` that walks the yaml once and returns `_ConfigEntry` rows carrying both the raw params and the built deployment. Every public entry point (`load_all_deployments`, `deployments_for_available_providers`, `all_expected_model_names`) goes through it. The reader is dependency-injected as `ConfigReader = Callable[[Path], str]` with `Path.read_text` as the default, so the new regression pin counts reads without patching Path or mocking anything. `test_deployments_for_available_providers_reads_yaml_once` pins the invariant: injects a counting reader that returns a fixed yaml string, calls the resolver, asserts the reader ran exactly once. Would have failed on the prior double-read shape.
…master key to the claude CLI subprocess Every compat cell shells out to the ``claude`` CLI and passes its API key as ``ANTHROPIC_AUTH_TOKEN``. Prior to this commit the cell code path resolved that key via ``require_proxy`` and got the proxy's master key back. A compromised ``@anthropic-ai/claude-code`` release (which the cell executes with network access to the proxy) could then have used the master key to create/delete keys, register rogue deployments, read spend logs, or drain budgets. Nothing about running a prompt requires any of that surface. The compat fixture now mints a session key with ``allowed_routes=["llm_api_routes"]`` and ``models=[the 15 registered compat names]``, stashes it in ``_COMPAT_CLI_KEY``, and tears it down alongside the deployments. Cells read it through a new ``require_compat_cli_credentials(compat_result, cli_key_provider=...)`` in ``_env.py``. The old ``require_proxy`` stays as-is for the fixture's own control-plane calls, which legitimately need the master key. Provider is dependency-injected end to end. ``_basic_messaging.run_basic_messaging_cell`` and every cell now take ``cli_key_provider`` as a kwarg; the production default binds ``claude_code.conftest._compat_cli_key_provider`` at call time so the fixture had a chance to populate its slot. Tests pass a static provider directly without any globals or monkeypatching. ``_cli_key.build_compat_cli_key_body`` is a pure function that returns the ``KeyGenerateBody``, kept in its own module so the body shape can be pinned without any control-plane I/O. Test coverage for the security invariants: - ``test_cli_key.py``: allowed_routes is exactly ["llm_api_routes"], models list mirrors what we passed in, alias is stable, forbidden route groups (management_routes, info_routes, spend_tracking_routes, admin_only_routes, sso_only_routes, self_managed_routes) are absent. - ``test_env_resolution.py``: ``require_compat_cli_credentials`` returns the CLI key not the master key; hard-fails with a specific message rather than silently falling back to the master key when the provider returns None or an empty string; the env-missing guard fires before the provider is called. - ``test_basic_messaging.py``: the ``api_key`` handed to the CLI runner equals the CLI key and is verifiably not the master key; a null provider hard-fails before the runner is invoked so the master key never reaches the subprocess. If the fixture is not active (pure unit run) the helper hard-fails rather than silently reusing the master key.
The last commit added a ``pytest.skip`` branch to ``require_compat_cli_credentials`` for the case where the compat fixture short-circuited (no provider credentials in env, so zero deployments were registered, so no CLI key was minted). That was the wrong instinct - the CLAUDE.md rules in ``tests/e2e/`` are explicit: cells never skip, and a missing credential is a real setup error the developer must see, not silence. Collapse the two failure branches back to a single hard-fail. The error message names the concrete env vars the fixture needs (``ANTHROPIC_API_KEY`` at minimum, plus ``AWS_ACCESS_KEY_ID`` / ``AZURE_FOUNDRY_API_KEY`` / ``VERTEXAI_PROJECT`` + ``GOOGLE_APPLICATION_CREDENTIALS`` for the other provider surfaces) so the fix is one export away without grepping the fixture.
…ardize Azure env names on AZURE_AI_* The scoped-CLI-key layer added last commit was fighting the actual deployment shape. The fixture filtered deployments by the pytest-runner's shell env, but provider credentials live in the docker container (via env_file), not in the developer's shell. So `deployments_for_available_providers(os.environ)` saw an empty env, the fixture skipped minting a key, and every cell hard-failed with "compat CLI key not available for this session" - blocking real tests to protect against a threat model (compromised claude CLI stealing the master key from local docker) that doesn't apply to the local-loopback proxy this suite drives. Rip the whole layer: delete `_cli_key.py` and its unit tests, remove `require_compat_cli_credentials` / `CliKeyProvider` from `_env.py`, drop the CLI-key mint / stash / teardown from the compat fixture, revert cells and `_basic_messaging.run_basic_messaging_cell` to `require_proxy`. Model registration + teardown stays (the fixture still POSTs `/model/new` for the 15 compat deployments and cleans them up on session exit). Env-name alignment stays, bash-pin fix stays, single-yaml-read refactor stays. Also standardize the Azure env-var names on `AZURE_AI_API_BASE` / `AZURE_AI_API_KEY` across the tree. The compat deployments target Azure AI Foundry (`azure_ai/` provider) and the prior `AZURE_FOUNDRY_*` name did not match how the external secrets manager is spelled. Renames: `test_config.yaml` (3 tiers x 2 fields), the security pins that list forbidden env vars in the cron pytest and version-probe blocks, the driver test that ensures the CLI subprocess never sees the raw provider secret, the compat-models unit tests, the cron VM env example and its embedded comments. `docker-compose.yml` also exports `AZURE_AI_API_BASE` / `AZURE_AI_API_KEY` alongside the existing `AZURE_API_*` so `azure_ai/` and `azure/` deployments both work without needing the override alias hack.
…f shell env Previously the fixture called ``deployments_for_available_providers(os.environ)`` to filter down to deployments whose provider credentials were set in the pytest-runner's shell. That was the wrong plane: the credentials live in the proxy container's env (via docker-compose ``env_file``), not the developer's shell. So the filter dropped every deployment, the fixture registered zero models, and every cell 400'd with ``Invalid model name`` even though the proxy could serve them. Fix: fixture now iterates ``load_all_deployments()`` and POSTs ``/model/new`` for all 15. Per-deployment ``/model/new`` failures are printed to stderr with the reason and do not abort the session - cells that target a deployment the proxy genuinely can't serve (missing cred on the proxy side) still 400 loudly on their own request path, which is the right signal. Delete ``deployments_for_available_providers`` and every helper that supported it (``_is_available``, ``_is_bedrock``, ``_is_vertex``, ``_has_all``, ``_has_any``, ``_referenced_env_vars``, ``_AMBIENT_AWS_ENV``, ``_AMBIENT_GCP_ENV``, and the ``_ConfigEntry`` wrapper whose only purpose was carrying the raw params to that check). Compat-model unit tests lose the 6 availability-gating tests and the read-once regression; keep the drift check, the count check, the frozen check, and the vertex-alias check. Net diff: 3 files, +50/-273.
…atrix Rename every virtual name and upstream reference from ``claude-sonnet-4-6`` to ``claude-sonnet-4-5`` across the ``claude_code`` tree. Includes the yaml deployment declarations, every cell's ``*_MODELS`` list, the unit-test fixtures and rate-limiter provider table, docstrings, and comments. Bedrock is the special case: sonnet-4-6 lived under the undated ``us.anthropic.claude-sonnet-4-6`` upstream id, but sonnet-4-5 is only served under the dated ``us.anthropic.claude-sonnet-4-5-20250929-v1:0``. Both bedrock deployments in ``test_config.yaml`` (invoke and converse) get the dated suffix appended. Anthropic direct / Azure AI / Vertex all use the clean ``sonnet-4-5`` tier suffix and rename cleanly. The drift-check test (``test_yaml_covers_every_cell_declared_model_name``) still passes, which is the invariant that catches any missed rename.
… deployments VERTEXAI_LOCATION in our shared env is ``global``, but Anthropic on Vertex is not served in that alias - it's only served in specific regions (us-east5 has all three tiers). Passing ``global`` yields ``FAILED_PRECONDITION: Publisher Model ... is not servable in region us-central1`` (litellm resolves ``global`` to the project's default region, which is us-central1 for the compat test project). Pinning the region in the yaml overrides the env-derived location so the deployments resolve regardless of what the surrounding infra defaults to.
…ith empty GH_AUTH_HEADER array
``run_daily.sh`` runs under ``set -Eeuo pipefail``. The release-pagination loop declares ``GH_AUTH_HEADER=()`` and only populates it when ``GITHUB_TOKEN`` is exported, so unauthenticated invocations (including the two ``test_run_daily_release_pagination.py`` cases that drive the actual snippet from ``run_daily.sh``) hit ``"${GH_AUTH_HEADER[@]}"`` while the array is empty. macOS bash 3.2 and some bash 4 versions treat an empty-array expansion as an unbound variable under ``-u`` and abort with ``GH_AUTH_HEADER[@]: unbound variable``, which is why both pagination tests were failing on developer laptops. Use the standard ``"${array[@]+"${array[@]}"}"`` guard so the expansion is elided when the array is empty and preserved verbatim when it is not.
Also unblocks the two ``test_run_daily_release_pagination.py`` cases I had previously mis-attributed to external GitHub API rate limiting.
…the ECR image now The daily compat-matrix populator was a GCP VM running a systemd timer that invoked ``cron_vm/run_daily.sh``: resolve the latest v*-stable litellm tag, sync a worktree, boot a fresh proxy on :4100, run ``pytest tests/e2e/claude_code/``, feed the results to ``cron_vm/build_matrix.py``, then ``gh repo clone litellm-docs`` and open a PR with a fresh compatibility-matrix.json. Everything about that flow is now handled by the ECR image build - the VM is redundant. Delete the whole ``cron_vm/`` directory (``run_daily.sh``, ``build_matrix.py``, ``litellm-compat-matrix.service``, ``litellm-compat-matrix.timer``, ``litellm-compat-matrix.env.example``) and the entire ``_publisher_unit_tests/`` tree that pinned invariants about those files (pagination logic, ``env -i`` credential scrubbing on the pytest subprocess, systemd unit isolation flags). ``matrix_builder.py`` stays - it's the reusable core the ECR flow can call directly if the image needs to publish a compat matrix. Docstring refs to ``cron_vm`` in conftest and the "publisher script" mention in matrix_builder updated to reflect the new reality. Net: 12 files, +8/-1446.
…eader at the compat deployment level Every deployment the long_context_1m cell exercises now carries ``extra_headers.anthropic-beta: context-1m-2025-08-07`` in ``test_config.yaml``. That's sonnet-4-5 and opus-4-7 across all five providers (anthropic direct, bedrock invoke, bedrock converse, vertex, azure ai foundry) — 10 deployments total. Haiku-4-5 rows are left alone because the tier caps at 200k and doesn't accept the beta. Setting it on the deployment (not just relying on the CLI's ``--betas`` flag) forwards the header on every request routed to those aliases, regardless of what the client sent. Belt-and-suspenders for the long-context cells and lets any downstream client that just knows the alias exercise 1M context without needing to know the beta name. ``models.py`` gets ``extra_headers: dict[str, str] | None`` on ``LiteLLMParamsBody`` so the pydantic body forwards the yaml key instead of pydantic's ``extra="ignore"`` default dropping it (which would have shipped a deployment with no beta header, silently reverting this to the pre-commit state).
…ID; grow LlmCapability to 15 dimensions The 75 compat cells under tests/e2e/claude_code/ carried no @pytest.mark.covers() markers, so none of them contributed to the coverage-registry rollup. The Grafana dashboard reported ``Core LLMs 14.5% covered = 8/55`` for months while three provider surfaces (bedrock invoke, bedrock converse, azure ai foundry) plus vertex-anthropic were fully exercised by the compat matrix. Add ``llm_claude_code_compat.yaml`` with 68 new ``llm.messages.<route>.<capability>.<streaming>.works`` rows that cover every (feature x provider) cell not already declared in ``llm_conversational.yaml`` (the seven anthropic-direct messages rows already there are reused). Attach the matching ``@pytest.mark.covers(...)`` to each of the 75 cell files. The registry's ``LlmCapability`` enum was ``basic | mid_conversation_system | prompt_cache_5m | service_tier | structured_output | thinking | tool_use | vision``, which covered less than half of what claude_code actually exercises. Extend it to 15 dimensions: adds ``count_tokens``, ``long_context_1m``, ``pdf_input``, ``prompt_cache_1h``, ``thinking_with_tool_use``, ``tool_search``, ``web_search``. Each new value is a real feature the compat matrix already probes and a distinct upstream capability we care about tracking regression against. Also fixes 10 ``basic_messaging_*`` cells that were missing ``import pytest`` (they only referenced ``pytest`` through the harness's ``run_basic_messaging_cell``); the covers marker needs pytest imported at module scope so the collector's ``iter_markers`` finds it. Coverage impact (measured with ``python -m coverage_registry.collector --strict``): Core LLMs 14.5% -> 67.5% (8/55 -> 83/123 cells). Total 18.4% -> 34.6% (58/316 -> 133/384).
…proxy The session autouse fixture activates whenever resolve_proxy() succeeds, which includes the legacy LITELLM_PROXY_BASE_URL / LITELLM_PROXY_API_KEY spelling. It then built its Gateway with build_gateway(), which reads LITELLM_PROXY_URL / LITELLM_MASTER_KEY through e2e_config. Under the legacy-only spelling e2e_config sees nothing and falls back to http://localhost:4000 with sk-1234, so /model/new registered the compat deployments on a different host and key than the cells drive through require_proxy; every cell then 400s with "Invalid model name" against a proxy that looks correctly configured. build_gateway() now takes the three endpoints as keyword args defaulting to the existing e2e_config values, so the other callers are unchanged, and the fixture passes the config the cells resolved. Both planes get the one URL the cells use, since the deployment is fronted by a single address that routes management and LLM paths itself.
Relevant issues
fixes #32548
Linear ticket
Pre-Submission checklist
Screenshots / Proof of Fix
Stage e2e session hit at 2026-07-15T12:27:04Z (Loki):
The directory is not missing.
test_bash_tool_restrictions.pycomputedREPO_ROOT = Path(__file__).resolve().parents[4], which resolves to the repo root on a dev laptop but to/inside the stage container (tests/e2e/ is mounted at /app/e2e/), so the join yields/tests/e2e/claude_code/tool_use— nonexistent. Collection interrupted, whole suite reported broken.Anchor demo (host vs container):
Local collection before / after on
tests/e2e/:New anchor pin (added in this PR) run against both branches:
Env-alignment: with only the primary suite-wide names exported, a migrated cell resolves successfully instead of dying on the legacy names:
Env-alignment: with no proxy env at all, cell hard-fails naming the primary spellings (legacy names still mentioned in the compat_result payload so the migration is visible):
Type
🐛 Bug Fix
🧹 Refactoring
Changes
Two commits, both landing in
claude_code/, both undertests/e2e/.The first commit is the collection-blocker fix.
CLAUDE_CODE_DIRintest_bash_tool_restrictions.pynow anchors atPath(__file__).resolve().parents[1](the siblingclaude_code/directory), which resolves to the same directory whether the file lives at/Users/.../tests/e2e/claude_code/_pr_gate_unit_tests/...or/app/e2e/claude_code/_pr_gate_unit_tests/.... Theparents[4]REPO_ROOT was inferring the repo root from the file's absolute depth, which is a property of how the code was mounted, not a property of the layout. A new regression pintest_claude_code_dir_anchor_is_layout_independentasserts the anchor lands on a directory namedclaude_codethat contains the pin file, which is exactly the property that broke.The second commit is the env-naming refactor. Historically every
claude_codecompat cell read its ownLITELLM_PROXY_BASE_URL/LITELLM_PROXY_API_KEYand duplicated the same 12-line "missing env, hard fail" block. The rest oftests/e2e/readsLITELLM_PROXY_URL/LITELLM_MASTER_KEYfrome2e_config.py, so anyone standing up a live proxy for one suite had to export a second spelling forclaude_code, and every cell repeated the same boilerplate.Resolution moves to
claude_code/_env.py.resolve_proxy()prefers the suite-wide names and falls back to the legacy pair so existing CI wiring on stage keeps working during rollout.require_proxy(compat_result)is the one-liner cells call to bind(base_url, api_key)or hard-fail with a message that names both spellings. 55 cell files,_basic_messaging.py, and the driver's own unit-test fixture now go through it.run_compat.shaccepts either spelling and normalizes to the primary names before invoking pytest.cron_vm/run_daily.shexports the primary names when launching pytest. Net diff for the refactor: 71 files, +370/-1240._pr_gate_unit_tests/test_env_resolution.pypins the resolution rules so a future edit cannot silently reintroduce the drift: primary names win on tie, legacy names still resolve when primary is unset, mixed URL-primary key-legacy still resolves, empty-string exports are treated as unset,require_proxynames both spellings in its error message. 11 tests, all pass locally.QA runbook
tests/e2e/claude_code/_pr_gate_unit_tests/test_bash_tool_restrictions.py::test_claude_code_dir_anchor_is_layout_independent - the pin file's directory anchor lands on the sibling
claude_code/dir regardless of how deep the repo is mountedcd tests/e2e && python -m pytest claude_code/_pr_gate_unit_tests/test_bash_tool_restrictions.py -vand expect34 passedparents[4]anchor when run from/app/e2e/)tests/e2e/claude_code/_pr_gate_unit_tests/test_env_resolution.py::test_primary_env_names_match_suite_wide_config - the primary names claude_code reads are exactly the ones
e2e_config.pyreads for the rest of the suitecd tests/e2e && python -m pytest claude_code/_pr_gate_unit_tests/test_env_resolution.py -vand expect11 passed_env.pyaway fromLITELLM_PROXY_URL/LITELLM_MASTER_KEYfails this testLive-proxy sanity: with only the primary envs set, a real cell resolves and runs (count_tokens is chosen because it does not need a running proxy)
unset LITELLM_PROXY_BASE_URL LITELLM_PROXY_API_KEYLITELLM_PROXY_URL=http://localhost:4000 LITELLM_MASTER_KEY=sk-1234 python -m pytest tests/e2e/claude_code/count_tokens/test_anthropic.py -qand expect1 passedLive-proxy sanity: with no proxy env at all, cell hard-fails naming the primary spellings
unset LITELLM_PROXY_URL LITELLM_MASTER_KEY LITELLM_PROXY_BASE_URL LITELLM_PROXY_API_KEYpython -m pytest tests/e2e/claude_code/vision/test_anthropic.py -qand expectLITELLM_PROXY_URL / LITELLM_MASTER_KEY not configuredFinal Attestation