Skip to content

fix(router): backport #39491 to stable/1.100.x so retry breadcrumbs stop retaining every earlier request - #40455

Merged
mateo-berri merged 2 commits into
stable/1.100.xfrom
litellm_backport_1_100_x_retry_breadcrumb_growth
Sep 9, 2026
Merged

mateo-berri merged 2 commits into
stable/1.100.xfrom
litellm_backport_1_100_x_retry_breadcrumb_growth

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

User Flow

Before: a team running the stable 1.100.x image behind a flaky deployment watches their proxy pod slow down over the day and then crash-loop on out-of-memory

  1. They send POST https://litellm-domain/v1/chat/completions with "model": "flaky-chat" and a normal 20KB chat payload; the flaky deployment times out and the proxy fails over to the healthy one
  2. The response is 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1 (the flaky deployment fails, the router moves to the healthy one), and their app carries on
  3. They keep sending the same request all day; every one still returns 200, but the pod's memory keeps climbing and never comes back down between requests
  4. GET https://litellm-domain/health/liveliness, which answered in a few milliseconds after boot, starts taking hundreds of milliseconds because the event loop is busy copying the growing breadcrumbs
  5. The pod hits its memory limit, the orchestrator kills it, their app sees 502s from the load balancer and 499s while it reboots, and the cycle starts again

After: the same traffic against the same config keeps a flat memory footprint and fast health checks

  1. They send POST https://litellm-domain/v1/chat/completions with "model": "flaky-chat" and the same 20KB payload; the flaky deployment times out and the proxy fails over to the healthy one
  2. The response is 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1 (the flaky deployment fails, the router moves to the healthy one), and their app carries on
  3. They keep sending the same request all day; every one returns 200 and the pod's memory footprint stays where it was after boot
  4. GET https://litellm-domain/health/liveliness keeps answering in a few milliseconds
  5. The pod is never killed, so their app sees no 502s or 499s

Relevant issues

Backports #39491 (merged to litellm_internal_staging as 7bc2d0b06e2, shipped in v1.101.0-rc.1) onto stable/1.100.x. Also carries the router hunk of 7c87451eadf, which binds the breadcrumb tuple to a Final local and marks the write-back # rebind-ok, so the port matches what staging runs today. The regression entered the line in 721227e9ee

Linear ticket

Resolves LIT-7392

What is included

  • fix(router): keep retry breadcrumbs per request and out of the request snapshot #39491 fix(router): keep retry breadcrumbs per request and out of the request snapshot, cherry-picked from 7bc2d0b06e2 on internal staging with the original author preserved
  • the litellm/router.py hunk of 7c87451eadf (chore(router): document the breadcrumb write-back and ratchet lint budgets), folded into the same commit; its budget JSON edits were not picked, since budgets are never edited on a PR branch
  • a9ea5713ab9 test(router): type the breadcrumb test helpers, a follow-up commit on this branch only: the three helpers arrived untyped from the source commit (they still are on internal staging) and Greptile flagged them here

Adaptation notes

The pick applied without conflicts. The only divergence from the source commits is the parameter and return annotations on the three test helpers in a9ea5713ab9. make check passes on the branch and pytest tests/test_litellm/test_router.py -k breadcrumb passes (6 tests)

Known noise on this line

tests/test_litellm/test_router_per_deployment_num_retries.py::TestNoProviderRetryAmplification::test_direct_completion_still_forwards_num_retries_to_provider fails in this rig at both the merge base ecc04bf8112 and the tip with the same assert 3 > (2 + 1), while a second pass ran the same file at both commits in a clean worktree and got 28 passed, so the failure is environment or ordering on this box and not from this PR. The other 90 tests in the router retry and fallback files pass at the tip

CircleCI at the tip: local_testing_part1 and local_testing_part2 fail on bedrock/cohere.command-r-plus-v1:0, which Bedrock now answers with "This model version has reached the end of its life"; tests/local_testing/test_completion.py and test_streaming.py still name that model on stable/1.100.x and no longer do on internal staging, and the same two jobs failed the same way at 76b5fec1c51a. litellm_router_testing failed once on tests/local_testing/test_router.py::test_router_timeout, which asks for a 0.0001s timeout and got a connection error first; the job passed at 76b5fec1c51a on the same router.py, and the rerun from failed passed it at this tip (CircleCI workflow dd9cde72), leaving the two Bedrock EOL failures as the only red jobs. e2e_openai_endpoints fails on tests/openai_endpoints_tests/test_e2e_openai_responses_api.py::test_bad_request_error, which expects a 400 for non-existent-model and now gets OpenAI's 404; that test no longer exists in this form on internal staging and nothing in this PR touches the Responses path

Release

v1.100.1 has not been cut yet: pyproject on this line already reads 1.100.1 from #40176, so there is no version bump here. Cutting v1.100.1 from the merged tip is the human-gated release step

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally
  • My PR passes all required CI/CD checks
  • My PR's scope is as isolated as possible
  • I have received a Greptile Confidence Score of at least 4/5

Screenshots / Proof of Fix

Both legs ran the same rig: a proxy booted from the named commit with --num_workers 2 on 127.0.0.1:34698 (no database), three openai/gpt-5-mini deployments where the two flaky-chat ones carry timeout: 0.02 so every request times out there and fails over to healthy-chat (num_retries: 2, disable_cooldowns: true, fallbacks), and 300 sequential POST /v1/chat/completions with a 21.8KB payload (max_tokens: 5) against real OpenAI. Memory is phys_footprint from /usr/bin/footprint, summed over the three processes listening on the port (the uvicorn parent and its two workers) and also printed per process, sampled after every request. A second loop hit GET /health/liveliness every 0.5s during the load and recorded each probe's time_total. The box was under heavy swap pressure from other work during both legs, so read the footprint columns as a trend, not as absolute numbers

model_list:
  - model_name: flaky-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
      timeout: 0.02
  - model_name: flaky-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
      api_base: https://api.openai.com/v1/
      timeout: 0.02
  - model_name: healthy-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
router_settings:
  num_retries: 2
  disable_cooldowns: true
  fallbacks: [{"flaky-chat": ["healthy-chat"]}]
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
python litellm/proxy/proxy_cli.py --config lit7392_config.yaml --port 34698 --host 127.0.0.1 --num_workers 2
PIDS=($(lsof -nP -iTCP:34698 -sTCP:LISTEN -t | sort -u))
fp() { for p in $PIDS; do /usr/bin/footprint $p; done | awk '/phys_footprint:/{v=$2; if($3=="GB") v*=1024; if($3=="KB") v/=1024; s+=v; per=per sprintf("%d/", v)} END{printf "%dMB(%s)", s, per}' }
while true; do echo "$(date -u +%T) liveliness $(curl -s -o /dev/null -w '%{time_total}' http://127.0.0.1:34698/health/liveliness)s"; sleep 0.5; done > liveness.log &
for i in $(seq 1 300); do
  out=$(curl -s -o /dev/null -w '%{http_code} %{time_total} %{header_json}' -m 300 -X POST http://127.0.0.1:34698/v1/chat/completions \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @lit7392_req.json)
  code=${out%% *}; rest=${out#* }; t=${rest%% *}; hdr=${rest#* }
  retries=$(echo $hdr | python3 -c 'import json,sys;h=json.load(sys.stdin);print(h.get("x-litellm-attempted-retries",["?"])[0], h.get("x-litellm-attempted-fallbacks",["?"])[0])')
  printf 'req %02d http=%s %ss retries/fallbacks=%s footprint=%s\n' $i $code $t "$retries" $(fp)
done

Before (ecc04bf)

Memory grows request after request on both workers and the health probe gets slower: the summed footprint climbs from 2412MB at request 50 to 2566MB at request 300 (worker A 786MB -> 897MB, worker B 799MB -> 886MB), and /health/liveliness reaches 0.45s

  1. Boot the proxy from ecc04bf8112 (the stable/1.100.x tip) and read the listening pids
proxy pids 96978 97917 97918 footprint_start=2406MB(827/771/808/)
  1. Send the 300 requests (every line is the request's status, latency, the two retry headers, and the footprint right after it, summed and per pid)
req 01 http=200 1.479340s retries/fallbacks=0 1 footprint=2428MB(827/793/808/)
req 50 http=200 0.744383s retries/fallbacks=0 1 footprint=2412MB(827/786/799/)
req 100 http=200 0.892596s retries/fallbacks=0 1 footprint=2440MB(827/791/822/)
req 140 http=408 6.368788s retries/fallbacks=? ? footprint=2495MB(783/884/828/)
req 150 http=200 1.327206s retries/fallbacks=0 1 footprint=2506MB(783/885/838/)
req 200 http=200 0.996486s retries/fallbacks=0 1 footprint=2521MB(783/881/857/)
req 250 http=200 1.074123s retries/fallbacks=0 1 footprint=2536MB(783/890/863/)
req 300 http=200 0.811366s retries/fallbacks=0 1 footprint=2566MB(783/897/886/)
  1. Summarize the health probes that ran alongside the load
python3 -c 'v=sorted(float(l.split()[2][:-1]) for l in open("liveness.log"));n=len(v);print(f"probes={n} p50={v[n//2]:.3f}s p99={v[int(n*.99)]:.3f}s max={v[-1]:.3f}s")'
probes=700 p50=0.016s p99=0.288s max=0.452s

299 of 300 requests returned 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1 (12 of them retried once or twice first); request 140 came back 408 after 6.4s from the upstream side

After (76b5fec)

Same rig, same config, same 300 requests: the footprint stays flat once the second worker finishes warming up (2350MB at request 50, 2362MB at request 300; worker A 783MB -> 786MB, worker B 784MB -> 793MB) and the health probe stays at a few milliseconds

  1. Boot the proxy from 76b5fec1c51a (this branch) and read the listening pids
proxy pids 36897 37591 38247 footprint_start=1780MB(783/760/237/)
  1. Send the 300 requests
req 01 http=200 2.476666s retries/fallbacks=0 1 footprint=2089MB(783/778/528/)
req 32 http=000 300.000773s retries/fallbacks=? ? footprint=2348MB(783/782/783/)
req 50 http=200 0.768697s retries/fallbacks=0 1 footprint=2350MB(783/783/784/)
req 100 http=200 0.788462s retries/fallbacks=0 1 footprint=2353MB(783/784/786/)
req 140 http=200 0.604206s retries/fallbacks=0 1 footprint=2355MB(783/784/788/)
req 150 http=200 0.984040s retries/fallbacks=0 1 footprint=2356MB(783/785/788/)
req 200 http=200 0.724198s retries/fallbacks=0 1 footprint=2358MB(783/785/790/)
req 250 http=200 1.099827s retries/fallbacks=0 1 footprint=2361MB(783/786/792/)
req 300 http=200 0.712271s retries/fallbacks=0 1 footprint=2362MB(783/786/793/)
  1. Summarize the health probes
python3 -c 'v=sorted(float(l.split()[2][:-1]) for l in open("liveness.log"));n=len(v);print(f"probes={n} p50={v[n//2]:.3f}s p99={v[int(n*.99)]:.3f}s max={v[-1]:.3f}s")'
probes=1056 p50=0.003s p99=0.146s max=0.283s

299 of 300 requests returned 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1 (5 retried once first). Request 32's upstream call to the healthy deployment stalled and curl gave up at its 300s limit; the 527 health probes sent during that stall averaged 22ms with a 0.28s max, so the event loop was serving while it waited, and the next request answered in 1.6s

Observations from the run:

  • Both legs: fallback fires without retries on the flaky timeout
  • One upstream stall per leg (408 before, 300s hang after), unrelated to breadcrumbs
  • After leg worker B needed about 50 requests to warm up
  • Footprint numbers are noisy on this swapped box; the trend is the signal

For reference only (a debugging callback, not part of the User Flow): on the before leg the breadcrumbs on the last request reached 78,564 nested containers and took 198ms to walk on one worker, against 94 containers and under 1ms on every request of the after leg

Dependents of the breadcrumb shape, stable tip vs this branch

The only in-repo reader of previous_models that decides anything with it is litellm.num_retries_per_request (litellm/utils.py:1451-1455), which compares the setting against len(previous_models). Same rig as above with num_retries: 1 and num_retries_per_request: 4 in litellm_settings, two workers, eight sequential requests per side

for i in $(seq 1 8); do
  curl -s -o body.json -D hdrs -w '%{http_code} %{time_total}' -X POST "http://127.0.0.1:25261/v1/chat/completions" \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @req.json
done

Stable tip ecc04bf8112: the router-wide breadcrumb list is already at its cap of 4 by the second request each worker sees, so the per-request check fires on the first retry and the request never reaches the fallback

req 01 http=200 1.235620s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJwqT3z14IbbREfehoXHnFwlVnPc",...
req 02 http=500 2.294009s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. Received Model Group=flaky-chat\nAvailable Model Group Fallbacks=['healthy-chat']...
req 03 http=200 1.828059s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJwuZwS3W6NLPsZXPJUBjN3PFT9S",...
req 04 http=500 2.309747s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. ...
req 05 http=500 2.264794s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. ...
req 06 http=500 2.478238s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. ...
req 07 http=500 2.049835s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. ...
req 08 http=500 2.463103s body={"error":{"message":"litellm.APIConnectionError: APIConnectionError: OpenAIException - Max retries per request hit!. ...

This branch a9ea5713ab9: every request carries only its own three breadcrumbs (two failed flaky-chat attempts plus the fallback hop, LIT7392CB success breadcrumbs=3 on all eight), so the check stays under 4 and the fallback answers

req 01 http=200 1.841973s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyCxjydlpWa0NLxA9FCJAmE7LHV",...
req 02 http=200 0.750124s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyDcU3e9QEL2hN8b5K0jZZJFSxi",...
req 03 http=200 0.716563s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyE0lNCYMAY9J2NjSUHgVjkcd0r",...
req 04 http=200 0.805491s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyFJYYlOUZmkEkXmyvJ1oK3iR1X",...
req 05 http=200 0.765768s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyGwDyCT28QPengrHs8X4dlR3oc",...
req 06 http=200 0.920845s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyHmbiBpxZGdzACqLjBLTov1XMo",...
req 07 http=200 0.932940s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyIav0Wqgvdm1MWizcgRep9RTDX",...
req 08 http=200 0.972566s retries/fallbacks=0 1 body={"id":"chatcmpl-EMJyJveVTZwRj158PifIBoXnJv7Xq",...

The other readers (litellm/integrations/s3.py, datadog/datadog.py, logfire_logger.py drop the key while cleaning metadata, langfuse/langfuse.py denies it as a steering key) never look inside the value, and the LIT7392CB custom logger above received the breadcrumbs on both sides. Spend log rows were not checked, since the rig runs without a database

Type

🐛 Bug Fix

Caveats (if any)

Low

  • previous_models is now a tuple, not a list, on the stable line too
    • every in-repo reader takes its length, strips the key, or JSON-dumps it; a custom logger that calls .append on it would break
  • Breadcrumbs no longer carry proxy_server_request
    • no reader in litellm/, enterprise/, ui/, or the tests indexes a breadcrumb for it; spend logs still read the snapshot off the request itself
  • litellm.num_retries_per_request now counts this request's failures only
    • on the stable tip it counted the router's last four failures across all requests, which is why the stable leg above fails 6 of 8 requests with that setting and this branch answers all 8
  • After a fallback hop, an SDK caller's own metadata dict stops collecting breadcrumbs
    • run_async_fallback hands the next hop a dict(...) copy on stable and staging alike; the proxy and every logger still see the full chain through that copy
  • The proof's memory signal is a trend on a swapped box, not a clean number
    • before grew about 0.6MB per failed request over 250 requests; after still drifted 9MB on one worker; the sharpest evidence is the 78,564 vs 94 container count filed as reference only

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

  • a9ea571 passes /live-pr-risk

…t snapshot

Backport of #39491 to stable/1.100.x. Cherry-picked from 7bc2d0b (litellm_internal_staging), with the router hunk of 7c87451.
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This backport removes Router-level retry breadcrumb state, keeps breadcrumbs scoped to each request, excludes the aliased proxy request snapshot, and retains only the four most recent attempts. Regression tests cover cross-request isolation, bounded retention, credential masking, and fallback walk-state exclusion

Confidence Score: 5/5

The PR appears safe to merge because retry breadcrumbs are bounded and request-local without breaking metadata object identity

No new actionable failures remain. The prior test-helper typing thread was manually resolved without explanation. The line-length dispute was correct because the cited line is within the configured 120-character limit

Important Files Changed

Filename Overview
litellm/router.py Replaces shared Router breadcrumb storage with bounded per-request metadata while preserving in-place metadata write-back
tests/test_litellm/test_router.py Adds regression coverage for breadcrumb isolation, flattening, retention limits, credential masking, and fallback behavior

Reviews (3): Last reviewed commit: "test(router): type the breadcrumb test h..." | Re-trigger Greptile

Comment thread tests/test_litellm/test_router.py Outdated
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri mateo-berri added run-ci and removed run-ci labels Sep 9, 2026
Comment thread litellm/router.py
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit a9ea571. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin e2e QA

Tested at: a9ea571
Session: https://app.devin.ai/sessions/1dbeace853cd432383d6436da7cbbae4

Both legs ran the same rig on a Linux box (32GB RAM, no swap pressure): a proxy booted from the named commit with --num_workers 2 on 127.0.0.1:44788 (a random high port confirmed free, no database, DATABASE_URL unset), the PR description's config verbatim (three openai/gpt-5-mini deployments, the two flaky-chat ones with timeout: 0.02 so they always time out and fail over to healthy-chat, num_retries: 2, disable_cooldowns: true), and 300 sequential POST /v1/chat/completions with a 21.3KB payload (max_tokens: 5) against real OpenAI. Memory is VmRSS from /proc/<pid>/status, summed over the three processes listening on the port (uvicorn parent plus two workers) and printed per pid, sampled after every request. A second loop hit GET /health/liveliness every 0.5s during the load and recorded each probe's time_total

model_list:
  - model_name: flaky-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
      timeout: 0.02
  - model_name: flaky-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
      api_base: https://api.openai.com/v1/
      timeout: 0.02
  - model_name: healthy-chat
    litellm_params:
      model: openai/gpt-5-mini
      api_key: os.environ/OPENAI_API_KEY
router_settings:
  num_retries: 2
  disable_cooldowns: true
  fallbacks: [{"flaky-chat": ["healthy-chat"]}]
general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
PORT=44788
python litellm/proxy/proxy_cli.py --config lit7392_config.yaml --port $PORT --host 127.0.0.1 --num_workers 2
PIDS=($(ss -ltnp "sport = :$PORT" | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u))
rss() { for p in ${PIDS[@]}; do awk '/VmRSS/{printf "%d\n", $2/1024}' /proc/$p/status; done | awk '{s+=$1; per=per $1 "/"} END{printf "%dMB(%s)", s, per}'; }
echo "proxy pids ${PIDS[*]} rss_start=$(rss)"
while true; do echo "$(date -u +%T) liveliness $(curl -s -o /dev/null -w '%{time_total}' http://127.0.0.1:$PORT/health/liveliness)s"; sleep 0.5; done > liveness.log &
for i in $(seq 1 300); do
  out=$(curl -s -o /dev/null -D hdr.txt -w '%{http_code} %{time_total}' -m 300 -X POST http://127.0.0.1:$PORT/v1/chat/completions \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H 'Content-Type: application/json' -d @lit7392_req.json)
  code=${out%% *}; t=${out#* }
  retries=$(tr -d '\r' < hdr.txt | awk 'BEGIN{r="?";f="?"} tolower($1)=="x-litellm-attempted-retries:"{r=$2} tolower($1)=="x-litellm-attempted-fallbacks:"{f=$2} END{printf "%s %s", r, f}')
  printf 'req %03d http=%s %ss retries/fallbacks=%s rss=%s\n' $i $code $t "$retries" $(rss)
done
python3 -c 'v=sorted(float(l.split()[2][:-1]) for l in open("liveness.log"));n=len(v);print(f"probes={n} p50={v[n//2]:.3f}s p99={v[int(n*.99)]:.3f}s max={v[-1]:.3f}s")'

Before (ecc04bf)

Both workers grow steadily and never come back down: worker A goes 435MB -> 520MB and worker B 460MB -> 515MB over 300 requests, about 19MB summed per 50 requests, with the slope unchanged at the end of the run

Retry-heavy /v1/chat/completions load with a flaky deployment that fails over

  1. Boot the proxy from ecc04bf8112 (the merge base) and read the listening pids
commit ecc04bf8112acfec0eeb8144eb88acf87e42b850
proxy pids 9065 9140 9141 rss_start=1330MB(461/434/435/)
  1. Send the 300 requests (every line is status, latency, the two retry headers, and RSS right after it, summed and per pid)
req 001 http=200 1.407152s retries/fallbacks=0 1 rss=1356MB(461/435/460/)
req 050 http=200 1.149586s retries/fallbacks=0 1 rss=1401MB(461/468/472/)
req 100 http=200 0.886461s retries/fallbacks=0 1 rss=1420MB(461/478/481/)
req 150 http=200 0.753935s retries/fallbacks=0 1 rss=1439MB(461/487/491/)
req 200 http=200 0.678870s retries/fallbacks=0 1 rss=1458MB(461/499/498/)
req 250 http=200 0.968792s retries/fallbacks=0 1 rss=1477MB(461/510/506/)
req 300 http=200 0.760427s retries/fallbacks=0 1 rss=1496MB(461/520/515/)

All 300 requests returned 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1

/health/liveliness latency across 2 workers during the load

  1. Summarize the probes that ran alongside the 300 requests
probes=521 p50=0.002s p99=0.003s max=0.019s

On this box (no swap, 300 requests) the growth did not yet slow the probe; the memory slope is the signal here

After (a9ea571)

Same rig, same config, same 300 requests: after the workers finish warming up around request 50, RSS is flat. Worker A goes 464MB -> 468MB and worker B 463MB -> 469MB from request 50 to 300 (10MB summed), against 95MB summed over the same span before

Retry-heavy /v1/chat/completions load with a flaky deployment that fails over

  1. Boot the proxy from a9ea5713ab9 (this branch tip) and read the listening pids
commit a9ea5713ab9f8af17e2abec43714ed37b4c710e4
proxy pids 18358 18448 18449 rss_start=1338MB(467/434/437/)
  1. Send the 300 requests
req 001 http=200 2.119526s retries/fallbacks=0 1 rss=1365MB(467/461/437/)
req 050 http=200 0.771620s retries/fallbacks=0 1 rss=1394MB(467/464/463/)
req 100 http=200 0.730291s retries/fallbacks=0 1 rss=1397MB(467/465/465/)
req 150 http=200 0.851946s retries/fallbacks=0 1 rss=1398MB(467/465/466/)
req 200 http=200 0.772110s retries/fallbacks=0 1 rss=1401MB(467/466/468/)
req 250 http=200 0.795036s retries/fallbacks=0 1 rss=1402MB(467/467/468/)
req 300 http=200 0.897509s retries/fallbacks=0 1 rss=1404MB(467/468/469/)

All 300 requests returned 200 with x-litellm-attempted-retries: 0 and x-litellm-attempted-fallbacks: 1

/health/liveliness latency across 2 workers during the load

  1. Summarize the probes that ran alongside the 300 requests
probes=502 p50=0.002s p99=0.003s max=0.007s

Verdict: the fallback path behaves identically on both legs (200, retries 0, fallbacks 1 on every request), and the per-request memory growth on the merge base is gone at the tip

@mateo-berri mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mateo-berri
mateo-berri merged commit e4e811c into stable/1.100.x Sep 9, 2026
49 of 53 checks passed
@mateo-berri
mateo-berri deleted the litellm_backport_1_100_x_retry_breadcrumb_growth branch September 9, 2026 21:27
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Sep 10, 2026
…00.1) (#544)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | patch | `v1.100.0` → `v1.100.1` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/141) for more information.

---

### Release Notes

<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>

### [`v1.100.1`](https://github.com/BerriAI/litellm/releases/tag/v1.100.1)

[Compare Source](BerriAI/litellm@v1.100.0...v1.100.1)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.100.1
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.100.1/cosign.pub \
  ghcr.io/berriai/litellm:v1.100.1
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- chore(release): backport [#&#8203;39568](BerriAI/litellm#39568) and [#&#8203;39572](BerriAI/litellm#39572) to stable/1.100.x and cut 1.100.1 by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;40176](BerriAI/litellm#40176)
- fix(router): backport [#&#8203;39491](BerriAI/litellm#39491) to stable/1.100.x so retry breadcrumbs stop retaining every earlier request by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;40455](BerriAI/litellm#40455)
- revert: drop the spend attribution backports from stable/1.100.x by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;40495](BerriAI/litellm#40495)

**Full Changelog**: <BerriAI/litellm@v1.100.0...v1.100.1>

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43NS4wIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicmVub3ZhdGUvY29udGFpbmVyIiwidHlwZS9wYXRjaCJdfQ==-->

Reviewed-on: https://git.hayden.moe/hayden/phoebe/pulls/544
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant