The old check_duplicate_issues.yml matched on title wording, so it missed the
same bug reported in different words. Over one full week of new issues (167,
5 to 12 Sep) it flagged 2, both wrong, while hand review found 11 real
duplicates that nothing caught.
The new workflow fetches the issue through the API into a file, runs
openai/codex-action with a fixed prompt and an output schema, and lets Codex
search the tracker with gh. At a 0.95 confidence gate it would have posted 12
comments that week, 9 naming a real duplicate. It reuses the same marker
comment and potential-duplicate label as before so auto-close-duplicates.yml
keeps working unchanged, and warns about the auto-close only when the titles
actually match.
Traffic goes through LiteLLM: the key is a virtual key and the endpoint is
the proxy's /v1/responses. Comments and labels stay off until the
DUPLICATE_CHECK_ENABLED repo variable is set.
Since #37391 every log record went through the secret-redaction regex twice, once in the
filter and again in the formatter, and the formatter pass ran on the whole formatted line. At
DEBUG level a multi-megabyte request body (a multi-page PDF upload to /v1/ocr) turned each of
those lines into ten seconds of synchronous regex work on the event loop, long enough for a
Kubernetes liveness probe to restart the pod mid-request.
The filter is now the complete scrubber (message, exception text, stack info, and extras) and
stamps the record, so the formatters skip records that are already clean. The stdout
truncation filter also collapses base64 runs longer than MAX_BASE64_LENGTH_STDOUT_LOG (4096
by default) at every level before the secret regex sees them, so a debug line carrying a
request body costs milliseconds instead of seconds.
* fix(proxy): bound tool and guardrail index create_many by the spend-log statement budgets
One flush drains up to MAX_LOGS_PER_INTERVAL source transactions or logs, but a
transaction fans out to one LiteLLM_SpendLogToolIndex row per tool and a log
to one LiteLLM_SpendLogGuardrailIndex row per guardrail, so the index
create_many payload was unbounded. Both index writes now go through
spend_log_write_batches(SPEND_LOG_WRITE_BATCH_MAX_BYTES, SPEND_LOG_WRITE_BATCH_MAX_ROWS).
The tool index write moves out of the rollup batch_() so the split reduces
the query-engine payload; replayed index rows are no-ops under
skip_duplicates, and the daily rollup upserts stay in one transaction
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(proxy): pin the row budget in the index fan-out tests
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>
Router.log_retry used to copy the failed attempt's kwargs and metadata into
metadata.previous_models. Nothing downstream read those copies, but they carried
client credentials into spend logs and grew the payload on every retry. Each
attempt now leaves a flat record (model group, deployment id, exception type and
string, attempt number), which drops RETRY_BREADCRUMB_EXCLUDED_KWARGS and the
per-retry credential masking.
num_retries_per_request was enforced from len(previous_models), which only
looked at the metadata bucket and never exceeded four records. The sync and
async client wrappers and the Rust lifecycle guard now read attempted_retries
from whichever metadata bucket the call carries.
Resolves LIT-7505
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The bridge probe asked `responses_api_bridge_check` with the summary read straight off
the Responses object, but `litellm.completion` reads it from `optional_params` via
`peek_reasoning_summary_aliases`, which the bridged request never populated. So gpt-5,
gpt-5.1 and azure/gpt-5 answered "bridging" to the probe and "not bridging" for real,
and the object still landed on Chat Completions, which only takes a string
`reasoning_effort` is now always the effort string, and `summary` rides the
`reasoning_summary` alias that main.py already reassembles into `{effort, summary}` on
the bridged path. The alias is emitted only when the probe says the model bridges, so
no chat provider ever sees it, and the probe is now asked with the exact params this
transform emits
The debug call built its message with an f-string, which
test_logging_calls_do_not_build_their_message_eagerly rejects. Pass the exception
as a %-style argument so the message is only built when the log is emitted.
The bridge probe called responses_api_bridge_check without api_base, so it
resolved the OpenAI base from globals and environment rather than from the
request, while litellm.completion runs the same check with the caller's value.
Today the two cannot disagree: this path always supplies a reasoning_effort,
which short-circuits the endpoint term in the only arm that reads it. Passing it
anyway keeps the probe a faithful mirror of the definitive check rather than one
that happens to agree.
The Responses API takes reasoning as an object, {effort, summary}. Chat
Completions takes reasoning_effort as a string enum and has no equivalent of
summary, but the completion bridge forwarded the whole object whenever summary
was set, which agentic clients set on every request.
Bedrock Converse guards its mapping with isinstance(value, str) and has no else
branch, so the object fell through, thinking was never enabled, and the caller
was billed for a non-thinking turn with nothing in the response to explain it.
The object is still forwarded for the one caller that can consume it: a model
whose cost-map mode is responses, which litellm.completion bridges back onto the
Responses API and reassembles {effort, summary} there. That decision is delegated
to responses_api_bridge_check, the same check litellm.completion runs, rather
than a second copy of the rule that could drift from it. An object carrying no
effort now yields no reasoning_effort at all.
Multi-IdP deployments can now set virtual_key_claim_field and
unregistered_jwt_client_behavior on a JWTIssuerConfig entry. Tokens from
that issuer use the issuer-specific claim path and no-match policy for the
virtual key mapping lookup; issuers that omit them keep the global values.
The auth flow now enters the mapping lookup when any issuer configures the
field, not only when the global field is set.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
/model/info fills a deployment's missing pricing in from the model cost map so the
Admin UI has a rate to display. Clients echo that whole model_info blob back on save,
and update_db_model merged it into the row, so editing an unrelated setting turned
that day's catalog price into a real per-deployment override. After that the
deployment ignored the cost map and Reload Price Data could no longer move it,
because the reload replays each deployment's stored pricing over the fresh catalog.
Drop the derived pricing from incoming model_info on the two write paths. The
drop-set is read off the same objects the read path uses, CustomPricingLiteLLMParams
plus the tiered *_above_N_tokens pattern that get_model_info passes through and no
model declares, so it cannot drift as new rates are added. output_vector_size is
exempt: it lives on the pricing model but is an embedding dimension, not a rate.
A deployment's own pricing still rides litellm_params, which is untouched, as is the
explicit-null clear, which reads the incoming model rather than the filtered dict.
The filter sits in the endpoint bodies rather than _add_model_to_db, which master-key
rotation reuses to re-serialize every stored deployment.
Codex hides catalog entries whose supported_in_api is false when it runs
with an API key, so a stock entry the proxy serves now carries
supported_in_api true alongside its list visibility.
`lite codex` now asks the installed Codex for its own model list through
`codex debug models` before writing the catalog. A proxy model whose id
matches a stock Codex slug keeps that Codex's entry (reasoning levels,
base instructions, context window and the rest) and only its picker
position, visibility and upgrade nudge come from the proxy. Unknown slugs
still get the plain entry built from the bundled base instructions. The
catalog directory is created before the stock call so a fresh CODEX_HOME
does not make Codex refuse to run
Drop the unrequested env override on LOGGING_WORKER_TIMEOUT_SUMMARY_WINDOW_SECONDS.
The constructor still accepts a value for tests, so runtime behavior is unchanged.