2026-09-20 The Quota Callback That Was Never a CustomLogger
What I set out to do
curl localhost:4000/v1/models refused the connection. Find out why LiteLLM was down.
What I actually did
The proxy was not down, it was crash-looping. launchctl print gui/$UID/ai.litellm.proxy showed runs = 33 and last exit code = 3 under KeepAlive, which restarts it fast enough that the port looks permanently closed. docker ps showed nothing wrong because LiteLLM is a host launchd agent, not a container. ~/.local/share/litellm/stderr.log had the real error:
ValueError: litellm_settings.callbacks entry 'litellm_codeassist_quota.handler' resolved to CodeAssistQuotaLogger, which is neither a CustomLogger instance nor a callable, so the proxy would never run it.
The cause was the quota callback I had been working on uncommitted. litellm_codeassist_quota.py declared class CodeAssistQuotaLogger: with no base class. Its sibling litellm_codeassist_usage.py gets this right at line 145.
Fixed it by importing CustomLogger and subclassing, then wrote test_handler_is_a_custom_logger as the regression guard. That test came first: the seven existing tests all passed against the broken class, which is exactly why it shipped. They exercise parsing and state, and nothing asserted the contract the proxy actually enforces.
Applied with hm switch, confirmed /health/liveliness 200, runs = 1, last exit code = (never exited). just check green.
Then verified the telemetry end to end, which was still unproven. The gauges only populate when agy polls retrieveUserQuotaSummary, so I triggered one with agy -p. Four buckets landed in SigNoz within a minute: gemini-5h, gemini-weekly, 3p-5h, 3p-weekly, each at remaining_fraction 1.0, with reset_time at 09:55 today for the 5h windows and 09-27 for the weekly ones. Labels agy.quota.bucket, .window, .group all correct.
What was striking
LiteLLM type-checks litellm_settings.callbacks at startup rather than duck-typing it. A class with the right method names, the right signatures, and a docstring saying “CustomLogger hook” is still rejected on isinstance. And the rejection is startup-fatal: the callback is not skipped, the whole proxy exits. One unloadable telemetry hook takes down every model route.
The SigNoz verification nearly produced a false negative. A 1h query returned nothing while a 6h query returned all four buckets, because the only datapoint sat in the current incomplete bucket and the default avg aggregation dropped it. timeAggregation: latest surfaced it. Worth remembering before concluding a new metric never arrived.