From 17e77949239f8a85f480b45846b5264a3f5acd7a Mon Sep 17 00:00:00 2001 From: nesquena-hermes <[email protected]> Date: Mon, 1 Jun 2026 00:14:13 +0000 Subject: [PATCH] fix(#3263): init _skip_cc_cl/_cc_cl before compressor block (UnboundLocalError on no-compressor path) + relax brittle #1318 source-assertion The threshold-rescale block runs unconditionally after the fallback and references _skip_cc_cl/_cc_cl, which were only defined inside 'if _cc_for_save:'. On the no-compressor path (fresh agent / interrupted stream) that raised UnboundLocalError (caught by test_issue1857_usage_overwrite). Hoist both inits above the block (no-op rescale when no compressor). Also widen the #1318 source-assertion test to accept the widened fallback gate (still asserts the falsy-check invariant). Co-authored-by: allenliang2022 --- api/streaming.py | 6 ++++++ tests/test_pr1318_context_length_fallback.py | 18 +++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/api/streaming.py b/api/streaming.py index 9360662d..2df398e4 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -5868,6 +5868,12 @@ def _run_agent_streaming( # block writes them to the session itself so GET /api/session # returns them on reload instead of falling back to 0. _cc_for_save = getattr(agent, 'context_compressor', None) + # Initialized before the compressor block so the #3256/#3263 + # threshold-rescale below is safe even when there is no + # compressor (fresh agent / interrupted stream): _skip_cc_cl + # stays False and _cc_cl stays 0, so the rescale is a no-op. + _skip_cc_cl = False + _cc_cl = 0 if _cc_for_save: _cc_cl = getattr(_cc_for_save, 'context_length', 0) or 0 # Same guard as routes._resolve_context_length_for_session_model: diff --git a/tests/test_pr1318_context_length_fallback.py b/tests/test_pr1318_context_length_fallback.py index ec4e3add..e564a19b 100644 --- a/tests/test_pr1318_context_length_fallback.py +++ b/tests/test_pr1318_context_length_fallback.py @@ -47,17 +47,21 @@ def test_fallback_uses_model_metadata(): def test_fallback_gates_on_falsy_context_length(): - """Fallback runs only when the compressor didn't populate s.context_length. + """Fallback runs when the compressor didn't populate s.context_length. - The gate must check s.context_length (not _cc_for_save) — if the compressor - set context_length but it was 0, we still want the fallback to fire. + The gate must still check s.context_length being falsy — if the compressor + set context_length but it was 0, we want the fallback to fire. (#3256/#3263 + widened the gate to ALSO fire on `_skip_cc_cl` — a non-default model whose + compressor carried the stale global cap — so the gate is now + `(not getattr(s, 'context_length', 0)) or _skip_cc_cl`. The falsy-check + remains; this test asserts that invariant is intact, not the exact spelling.) """ block = _persistence_block() - # The conditional must reference s.context_length (or getattr(s, 'context_length', 0)) + # The conditional must still reference s.context_length being falsy. assert ( - "if not getattr(s, 'context_length'" in block - or "if not s.context_length" in block - ), "Fallback must gate on s.context_length being falsy" + "not getattr(s, 'context_length'" in block + or "not s.context_length" in block + ), "Fallback must still gate on s.context_length being falsy" def test_fallback_passes_model_and_base_url():