chore(ci): promote internal staging to main - #37721
Conversation
* feat(proxy): enable audit logs by premium license Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): support premium audit logging mocks Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): disable audit logging for key rotation mocks Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yucheng <yucheng@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
feat(ui): standardize the Teams page header
lite login used to write the minted cli-session key in cleartext to ~/.litellm/token.json. The secret material (key plus any JWT) now goes to the OS keychain through the optional keyring package, with the 0600 file kept for non-secret metadata and as the fallback on headless boxes. Legacy plaintext files keep authenticating and are migrated into the keychain, then scrubbed, on first read. A secret still on disk always outranks the keychain entry, so a failed keychain write can never resurrect a stale key. LITELLM_PROXY_API_KEY and --api-key precedence is unchanged, lite logout clears both stores and warns when the keychain will not release the entry, and ~/.litellm is created 0700 (tightened from 0755 where an older CLI left it broader). LITELLM_CLI_DISABLE_KEYRING=1 forces the file fallback.
…ml (#37556) * feat(ptu): accrue flat cost for PTU deployments declared in config.yaml The flat-cost rollup reads deployments from LiteLLM_ProxyModelTable, and config.yaml models never reach that table by design, so a PTU deployment declared there accrued no flat cost at all while still billing its traffic per token. The provider bills the reservation whichever file declared it. The rollup now also reads the deployments the router holds that no database row owns, identified by db_model, skipping the per-request credential clones that carry original_model_id and reuse their source's PTU config under a fresh id. Registering such a deployment zeroes its pricing, since reserved capacity already pays for the traffic it serves, and leaving a rate unset falls back to the public cost map, which makes the double charge the default rather than an opt-in. The rules both halves apply now live in one module. The rollup's test for what it will charge and the router's test for what to zero have to agree, or a deployment one accepts and the other declines serves its traffic for free. That module also owns the fields the write endpoints already zero, so the two paths cannot drift: tiered_pricing is emptied rather than zeroed because its tiers outrank the rates beside them, the search context table is written zeroed because an absent one means the provider default, and any further rate the deployment itself declares is zeroed alongside the standing set. The prune is bounded to the deployments a run scanned, but only for a run that priced a config-declared deployment. Deciding a row is garbage on staleness alone stays correct while every run derives its charges from the same table, so a database-only run sweeps exactly as it did before; once one host's charges come from a file the others cannot read, a row it never considered is not evidence of anything. Behaviour change worth calling out: a zeroed deployment sorts ahead of an unpriced sibling in QualityRouter's cost tiebreak, where an unset rate previously sorted last. Reserved capacity really is the cheaper choice, but the ordering moves. * refactor(ptu): drop a Final rebind and two redundant isinstance guards The basedpyright budget rejected reassigning a Final in the datetime coercion and two isinstance calls the router entry's own type already guarantees. Filtering the built records rather than the raw entries removes both guards and leaves _router_deployment as the single validator.
Drop the inline notes on keychain erasure and disk-vs-vault precedence in favour of docstrings on the two functions that own those rules, and remove a stale section header and a field note that the code already says plainly.
…ve their counters
…itellm_batch_enqueued_token_limit
lite ships with every install of litellm, but the keyring package it needs for keychain storage only ships with the cli extra. Such a user on a Mac was told 'No OS keychain available' about a machine that plainly has one, with nothing pointing at the missing package. The vault now reports which of the three unusable states it is in, so login can point at the install, name the kill switch, or report a genuinely absent keychain.
…itellm_cli_refresh_tokens # Conflicts: # basedpyright-code-budget.json
…37569) Converts the remaining dashboard components that still imported antd: admin panel, agents, MCP toolsets, policies, prompts, bulk user edit, create user, plugin settings, teams, add model, auto router, cloudzero export, BYOK credentials, credential modal, onboarding link, create key and routing groups. Primitives map onto the house shadcn set: Typography onto semantic tags, Select onto ui/select, SearchSelect or MultiSelect, Input onto ui/input, Tooltip onto SimpleTooltip, Card, Table, Tabs, Switch, Checkbox, Radio, Tag onto Badge, Divider onto Separator, Spin onto UiLoadingSpinner, Modal onto Dialog, message onto toast, and Space, Row, Col, Flex and Layout onto flex containers.
Migration moved the secret into the keychain and then suppressed any OSError from rewriting token.json, so a file that could not be rewritten kept the credential in cleartext while every command reported success. That file is now removed instead: signing in again costs one command, a stranded live credential costs the credential `lite logout` also reported a clean logout whenever the keyring package was missing, on the reasoning that an install without it could never have stored anything. The entry belongs to the OS, so a keychain-backed login survives a logout run from a venv without the cli extra. erase() now reports which keychain state applies, and logout warns with the advice that fixes each one, staying quiet for file-backed logins whose token file still carries its own secret Also pins the migration path's tightening of a world-readable legacy token.json, and moves the logout tests off patch() onto the injected vault
… be removed Removing the file when it could not be rewritten covered a full disk, but not a ~/.litellm that permits neither the rewrite nor the delete, which is what a `sudo lite login` leaves behind. There the secret was copied into the keychain and kept in cleartext on disk, so migration widened exposure instead of narrowing it Migration now only keeps the vault copy if the file's copy is gone. When it is not, the write is rolled back and the user is left exactly as they were, logged in with one copy of the credential
Nothing in the dashboard renders antd any more, so the package and the
scaffolding around it can go. This removes `antd` and
`@ant-design/cssinjs` from package.json, deletes the global StyleProvider
the root layout wrapped every page in, drops the `antd` cascade layer and
the z-index override that lifted Base UI popups over an antd Modal, and
retires the lint rules that policed antd imports and antd class selectors
in tests.
Fifteen test files still carried `vi.mock("antd", ...)` factories for
components that stopped importing antd during the migration. They were
inert, and they resolve the real module, so they would have broken the
moment the package left node_modules.
The compatibility shims keep their behaviour and lose the antd name:
`antdRules`/`antdRequired` become `validatorRules`/`requiredRule`,
`isAntdUrl` becomes `isValidUrl`, and `ABOVE_ANTD_MODAL` becomes
`NESTED_DIALOG_LAYER`. Comments that explain why a contract looks the way
it does still name antd, because that history is the reason.
…t done A keyring backend can accept a write and keep nothing. That is exactly what `keyring --disable` and PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring select, and it raises nothing to distinguish itself, so `lite login` was handing the credential to a black hole, scrubbing its own copy from token.json, and printing a success message over a login that no longer worked. Reading the value back is the only way to tell that backend apart from a keychain that really stored the secret. The same rule closes the rest of the gaps. A credential the token file will not record is taken back out of the keychain instead of being left live on a machine with no record of it, and is reported rather than raised. The migration stages its scrubbed file before the keychain is handed anything, so a directory that will not accept the rewrite stops the move rather than leaving the secret in two places. Logout no longer reads a key in the file as proof that the keychain is clear, which was never sound across two separate runs, and only draws that conclusion when the `keyring` package is missing outright, where nothing could have reached a keychain at all.
…mpt managers that can't run them UI-injected empty vector_store_ids/tags/guardrails on a DB model tripped the dynamic-param check, and the prompt-management fallback then handed the request to the first registered prompt manager (e.g. a saved dotprompt), whose sync path raised "prompt_id is required" as a 500 on every /chat/completions call. Empty dynamic params no longer count as a trigger, the fallback skips managers whose should_run_prompt_management declines a None prompt_id, and the sync base path returns the request unchanged for a None prompt_id like the async path.
Arize Phoenix claimed it could run without a prompt_id while its compiler requires one, so the no-prompt_id fallback could select it and fail instead of reaching the vector-store hook. It now declines like the other managers.
…kens (#37576) * refactor(ui): map hardcoded Tailwind palette classes onto semantic tokens The dashboard painted itself with literal palette utilities (text-gray-500, bg-blue-50, border-red-200) that resolve to one fixed color regardless of theme, so the shadcn token layer and its .dark block could never take effect. A codemod (scripts/codemod-color-tokens.mjs) rewrites 3,232 of those across 254 files onto the existing token scale: neutrals become foreground / muted-foreground / muted / border / card, and red, green, amber and blue collapse into destructive, success, warning and info, with the pale -50 to -300 tints expressed as opacity modifiers on the same token. Hover and focus variants map to accent so they lift rather than recess. 210 now-redundant dark: variants are dropped since the tokens carry both modes. The .dark palette is retuned to a neutral gray ramp with the sidebar recessed below the content canvas, replacing the blue-tinted shadcn default where the sidebar read as a full-height card floating on a near-black page. Nothing sets the .dark class yet, so light mode is unchanged and dark mode stays inert until a theme toggle lands. * chore(ui): drop the one-shot color-token codemod script
…serialise (#37571) * fix(ptu): hand the prune a plain delete filter the query builder can serialise The bounded sweep built its predicate as a read-only mapping view, which the query builder refuses to serialise, so the nightly job raised as soon as a config-declared deployment was priced. The charges were already written by then, which is why the run looked like it had produced its rows. The in-memory table these tests run against accepts any mapping, so only a live run caught it. A predicate builder now returns a plain dict and is asserted as one, and the catch-up pass has a test covering a config-declared reservation. * refactor(ptu): build the prune predicate in one shot Both filter shapes are known upfront, so the bounded one is constructed directly rather than by mutating a value already declared Final. The catch-up test took two independent clock reads, which disagree across UTC midnight; it now derives both the reservation start and the expected last charged day from a single read, matching the three sibling tests.
…imit feat(proxy): enqueued-token rate limiting for batches with refund on completion and cancellation
…ken migration (#37579) * fix(ui): restore hover feedback and dark-mode variants lost in the token migration PR #37576 mapped hardcoded Tailwind palette classes onto semantic tokens. Two-tone hover pairs collapsed onto a single token, so 116 hover utilities across 49 files became identical to their base class and produced no visible feedback, and in seven files a dark: variant was dropped while its hardcoded light partner survived, leaving those elements stuck light in dark mode. Hover states now follow the alpha-step idiom the shadcn primitives already use (hover:bg-primary/80, hover:bg-success/20): a duplicated hover:text-X or hover:bg-X becomes /80, hover:border-border becomes hover:border-ring, and a duplicate is dropped where another hover utility on the element already carries the change. One transition-colors that no longer animated anything is removed. For the dark-mode gaps, indigo maps onto info and amber onto warning. There is no purple token in globals.css, so the purple sites keep their palette classes and get their dark: partner back. * fix(ui): add an eslint rule that fails a hover: utility identical to its base The token migration collapsed two-tone hover pairs by hand, so nothing catches the next one. `local/no-noop-hover-variant` reads every string literal and template chunk and errors when a `hover:X` sits alongside a bare `X`, which is exactly the shape that renders no hover feedback. It ships at error with no suppression baseline, so the eleven sites that already carried a dead hover before the migration are fixed here too. The rule reads one class string at a time, so a base class supplied by a different ternary branch than its hover partner is left alone: a selected row whose resting colour already matches its hover colour is deliberate, not a bug.
…rc/ (#37563) The UI unit test job narrows a pull request to `vitest related <changed files>`. `related` maps a file to the tests that import it, so a file no test imports maps to nothing, and `--passWithNoTests` turns that empty selection into a green job. package.json, package-lock.json, the Vitest, Tailwind and TypeScript configs and tests/setupTests.ts are all in that category even though each of them can change the behaviour of every test in the suite, so a dashboard dependency bump merged having run no unit tests at all and only got real coverage later, from the full run on the push to litellm_internal_staging. Keep `related` for the common case where a pull request only touches files under src/, and fall back to the full suite as soon as one changed file sits outside it. The decision lives in .github/scripts/select_ui_test_scope.sh so it can be tested on its own, next to the existing classify_changes.sh gate.
…cleanup (#37580) PR #37579 read `text-X hover:text-X` on a shadcn Button as dead weight and removed the hover half. On the ghost and outline variants it was not dead: both carry their own `hover:text-foreground`, and the duplicate in the className was the thing displacing it through tailwind-merge. Dropping it handed the hover back to the variant, so the Remove button in a team's logging settings, the chat storage banner's dismiss control, and the collapsed enterprise-usage rail all lose their colour the moment you point at them. Each of the three now carries a distinct hover value, following the alpha-step idiom the rest of that migration used, which restores the colour and keeps `local/no-noop-hover-variant` satisfied. Every other hover utility that PR dropped sits on a plain element or a variant with no competing `hover:text-`, so those stay as they are.
`make lint` hands every path in the diff against the base branch to `ruff format --check`, including the ones the branch deleted, so any branch that moves or removes a file under `litellm/` fails the gate with "No such file or directory" instead of a formatting complaint. test-linting.yml already filters those out with `--diff-filter=ACMR`, so the Makefile was the half that drifted. Match it.
…t answer Three ways the credential commands could mislead or hang. `lite logout` on a machine that never logged in warned that a credential may be stranded in a keychain it could not check, and told the user to install keyring to go clear it. There was nothing there. A missing token file is now read as the evidence it is, because logout keeps a secret-free file behind whenever the keychain is left unconfirmed, so a later run can tell a machine with a credential it cannot reach apart from one that never had a login. That holds on the LITELLM_CLI_DISABLE_KEYRING path too. `KeyringDiscardsWrites` was handled on the read and erase paths, which cannot produce it: the null backend returns None from `get_password` rather than raising, so only a write ever detects it. It now lives on `SecretWrite` alone and the unreachable arms are gone. `keyring.set_password` blocks forever under a HOME with no usable login keychain, which is what containers, CI images, `sudo -H`, and service accounts run with, and reads answer normally there so nothing cheaper tells them apart. `lite login` never touched a keychain before this, so a sign-in that simply never returns would be a new way for it to fail. Writes are pre-flighted with a throwaway value on a bounded wait, and a keychain that stays silent falls back to the token file. The real credential is never the thing handed to a call that might land long after we stopped waiting. Saving also stages the token file before the keychain is given anything, since the file is the half a read-only or full directory refuses. A save that cannot land now leaves both stores as it found them, which matters most when the login it failed to replace still works.
…name Staging the token file can succeed and the replacement still fail afterwards, and that is the one save path where the keychain has already taken the new secret. It was reported as a save that kept nothing, which sends the user looking for a credential that is sitting in their keychain, and it claimed the previous login was untouched when the one keychain slot had just been written over. Give that path its own outcome and its own notice. The new secret stays where it is: the entry it replaced went the moment it landed, so no rollback brings that back, and removing the new one too would turn a login this machine may still be able to use into no login at all. The remaining `CredentialNotSaved` paths all leave both stores untouched, so the reassurance they carry is now true wherever it is printed.
Seven live e2e tests covering cost-tracking regressions that currently ship unnoticed: cache-write tokens billed at the cache-creation rate (#34046), per-component cost_breakdown on the spend row (#31686), cache reads billed at the cache-read discount on streamed calls (#34812), cache tokens surviving the anthropic-messages to Responses bridge (#34957), priority-tier rates applied to input, output and reasoning (#35923, #35925), the per-component response cost headers summing to the total (#36965), and cost injected into the final usage frame of an /openai passthrough stream (#36503). Every test registers its own deployment with a distinct custom rate per component, so a component billed at the wrong rate cannot pass. The shared helpers in cost_rows.py encode the one thing the two surfaces disagree on: the spend row's input_cost is gross of cache while the response's cost-input header is net of it.
…yments The /vertex_ai/live WebSocket passthrough only ever looked at default_vertex_config and the DEFAULT_VERTEXAI_* env vars, so a proxy whose Vertex credentials live in the DB as a model entry with use_in_pass_through had nothing to authenticate with. The upgrade still succeeded and the socket then closed with a bare 1000 on the first client frame, which gave the client no way to tell a misconfiguration from a normal end of session. Credentials now also resolve from the router deployments flagged use_in_pass_through, preferring the one matching the requested model, and a failure to mint an access token closes 1011 with a reason naming both ways to configure it. Upstream closes other than a plain 1000 are relayed to the client with their code and reason, so Google's own errors reach the caller. The setup frame's model is rewritten to the full projects/.../publishers/google/models resource path, which is what Vertex expects and what lets a bare model id or a gateway alias work over this route.
…ailures loud A /v1/realtime connection to a Vertex AI Live model accepted the WebSocket upgrade and then went silent: a stalled Google OAuth token fetch blocked the handler before any session event, and the eventual failure closed the socket with a bare 1011 and no error event, so callers saw an open socket, no frames, and no reason. Bound the pre-session token fetch with REALTIME_CREDENTIAL_RESOLUTION_TIMEOUT_SECONDS (20s default) and, on any realtime failure, send an OpenAI-style error event before closing with a reason that names the failure. Close reasons are truncated by bytes, not characters, since an over-long reason makes the close frame itself fail.
feat(fal_ai): add gpt-image-2 image generation support
…tes (#37700) SCIM roster writes were swallowed, so a group or user push returned 200 while the team roster never received the membership. Surfacing the failure fixes that, but aborting on the first failed write leaves the rest of the batch unattempted on top of unrolled-back, which is worse than what it replaces. Every roster write in a reconciliation is now attempted, and the ones that did not land are reported together, naming each failed add and remove. Rollback would be the other option and it is not safe here: the compensating write can fail too, and it can strip a membership that pre-dated the push. SCIM reconciliation is idempotent, so a named partial failure is what the IdP's next push needs to close the gap. The reported status still follows the failures, so a unanimous 404 stays a 404 and only a batch whose failures disagree falls back to 500. Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ial_provider fix(redis): apply Azure AD and GCP IAM auth to every async client path
…37744) a369cb0 made the chat-to-responses bridge return the routing prefix on the model it passes to responses(), so responses() re-resolving the provider is a no-op instead of stripping a second prefix. It updated the bridge's own unit tests but not this one, which still asserted the stripped id and has been failing llm_translation_testing since that change landed. The provider still receives gpt-5.4: responses() strips the openai/ prefix on its own resolve, one layer later than this assertion used to sit. The stale comment claiming the prefix is stripped before routing goes with it.
…ow scan (#36497) Every pod schedules the budget reset job, so a fleet re-read the whole due population and wrote it back against one Postgres at the same calendar boundary, multiplying a single sweep by its replica count. The job now takes the shared PodLockManager lease, so one pod sweeps per tick. A deployment with no Redis keeps its previous behavior, and a Redis that cannot answer sweeps unguarded rather than stranding every expired budget at its cap. The per-window scan read every row carrying budget_limits in one statement, so its cost grew with the deployment's key count. It is now keyset-paginated and walks to the end of the table on every sweep. A per-run cap would need a resume position, and no pod can hold one because the lease rotates between ticks, so the strictly advancing cursor is what terminates the walk. Found and updated rows were also JSON-serialized into the service hook's metadata and into debug lines on every chunk, on the event loop, whether or not anything consumed them. The hooks now carry counts, and the debug payload is deferred until a record is actually emitted. Resolves LIT-4793 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
feat(proxy): add admin toggle to block requests for models without pricing
…tring The passthrough tests and their coverage registry rows pointed at the internal ticket id, which does not resolve for anyone following a link from status.litellm.ai. Each test docstring and registry rationale now names the GitHub issue it pins: #36086 for the two prefix routing cases, #36087 for the file list cursors, #36523 for streamed Responses cost, and #36646 for embeddings spend.
…itellm_lit_5870_passthrough_e2e_pins_wt
The swe-1.7 rates were carried over from the closed prior attempt and match SWE-1.7 Lightning, 5x the SWE-1.7 Max and Medium rates the vendor publishes. swe-1.6 was already on the standard tier, so the two entries disagreed with each other. Both now read 0.5 in, 2.5 out, 0.2 cached per million tokens. Also drops the redundant registry comment in constants.py.
…dated_20260820 fix(model_prices): consolidate eleven open registry audits into one changeset
…upport The swe-1.7 rates were briefly lowered to the standard tier. The docs page records the API-served swe-1.7 as the Cerebras-served Lightning tier, so put the matching rates back rather than have the cost map and the docs disagree. Cognition also answers /v1/responses through the chat-completions bridge, the same as every other provider in the JSON registry, so the endpoints support matrix should say so instead of under-declaring it.
The Model-Specific Limits rows now carry Input TPM and Output TPM, and a limit the operator removes is sent as an explicitly empty map so /project/update actually drops it instead of leaving the stored quota enforced behind a UI that shows it gone. Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…hought_signatures fix(vertex_ai): only fall back to a placeholder thought signature on the first parallel function call
The support matrix says cognition serves /v1/responses, but the README row left that column blank, so the two disagreed. Every other provider row tracks the matrix, so bring this one in line.
* test(lint): ban blind pytest.raises(Exception) with ruff B017 A bare pytest.raises(Exception) accepts whatever the body throws. The TypeError a refactor introduces satisfies it exactly as well as the rejection the test was written for, so the crash reads as a pass and the test never goes red. All 111 existing sites are narrowed here. A runtime probe recorded the concrete exception each one actually catches, and each site now names that type. Where the code under test genuinely raises a bare Exception, the site pins a stable slice of the message with match= instead. Two sites tell on themselves. The shared responses-API cancel test raises "custom_llm_provider is required but passed as None" rather than talking to a provider at all, because cancel_responses takes a provider, not a model. And test_bedrock_guardrails_with_streaming was the only test in its file still passing without AWS credentials, because the NoCredentialsError boto3 raised long before the guardrail ran satisfied the blind raises. * fix(test): widen the openai batch-dispatch assertion to OpenAIError The narrowed NotFoundError only holds where OPENAI_API_KEY is set. Without one the SDK raises OpenAIError while building the client, long before any 404, so CI went red. OpenAIError covers both and still rejects a TypeError from a refactor.
…ncomplete_500 fix(responses_bridge): map incomplete responses to finish_reason length instead of 500
…ntity feat(cognition): give Cognition its own provider identity
…_embedding_timeout fix(caching): bound the semantic cache embedding lookup so a dead embedding endpoint can't block requests
…back (#37749) a369cb0 made completion() hand the prefixed model back to responses(), so that responses() running get_llm_provider() a second time becomes a no-op instead of stripping a prefix the model id owns. That was deliberate, and it shipped with its own unit test, but it left two older assertions behind still expecting the bare id. #37744 corrected the openai one in test_openai.py. This is its azure sibling, which llm_translation_testing has been failing on ever since. Only the expected value moves. The neighbouring custom_llm_provider assertion already passes and stays as it is.
…ves (#37746) Every live together_ai call in CI has answered 503 Service unavailable since 2026-08-20, across two runs 2.5 hours apart, while Together's status page reported no incident in either window. These are real calls, not replayed cassettes: the VCR layer runs filter_non_2xx_response, so a 503 is never written to a cassette and cannot be replayed back. Qwen/Qwen2.5-7B-Instruct-Turbo does not appear anywhere on Together's monitored component list, whose Qwen entries are all Qwen3.x, so a model-level outage there would never surface as an incident. The same 503 already forced test_basic_rerank_together_ai to be skipped on a different together_ai model, so per-model 503s are an established failure mode here rather than a platform outage. openai/gpt-oss-20b is the cheapest together_ai entry that carries real pricing and the capabilities these suites exercise, at $0.05/$0.20 per 1M tokens with function calling, response schema and tool choice. Together monitors it as a served component. The retired model also carries null pricing in the cost map, which is its own liability now that unpriced models are blocked. test_multiple_deployments.py keeps the old id: it is a router fallback list that is green today, and busting its cassette to prove a point would trade a passing test for a live call this change cannot vouch for.
…2e_pins test(e2e): pin openai_passthrough routing, cost logging, and file list isolation
tin-berri
left a comment
There was a problem hiding this comment.
Approving per Tin — this is the periodic staging→main promotion, already approved by yucheng-berri, and all bundled content was individually reviewed when it landed on staging. Note: 4 checks (Analyze (python), image-scan, lint, runtime-image) were showing failing as of my last check; image-scan is the confirmed repo-wide base-image CVE issue (non-blocking), but I hadn't finished confirming the other 3 before this approval — flagging in case any of those turn out to be a real regression worth a fast-follow.
|
Follow-up on the CI failures I flagged in the approval comment — dug into all 4:
Net: this means non-root deployments (OpenShift restricted-v2, k8s runAsNonRoot, air-gapped/readOnlyRootFilesystem) will hard-fail their migration entrypoint after this merges, where before it silently continued. Worth a fix to the runtime image's |
TLDR
Problem this solves:
How it solves it:
User Flow
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Type
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
Caveats (if any)
QA runbook
Final Attestation