fix: guard against cached context_length=0 poisoning resolution chain - #25812
fix: guard against cached context_length=0 poisoning resolution chain#25812OmarB97 wants to merge 1 commit into
Conversation
088cd42 to
f674a25
Compare
Rebased onto upstream/main as a single commit to refresh PR NousResearch#25812.
f674a25 to
ebad0dc
Compare
d62ed5e to
7d6d3a9
Compare
|
Refreshed this branch onto current upstream main as a single refresh commit (transplant — the branch predated the history replacement, so there was no merge base to rebase across). The change is identical to the original diff except for one conflict resolution in |
|
Provenance note for today's force-push: the branch was rebuilt as a single refresh commit ( |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for refreshing this focused cache-boundary fix. The numeric cached-zero premise is confirmed on current main: agent/model_metadata.py:2110-2111 accepts any non-None cache hit and :2173-2178 returns it directly.
Problems
- The cache guard does not cover the sibling live-metadata path.
_resolve_endpoint_context_length()accepts0as an integer atagent/model_metadata.py:1060-1062, and the custom-endpoint branch returns it at:2213-2215. - The new
cached <= 0comparison assumes YAML cache values are numeric._load_context_cache()returns raw YAML data (:1078-1080), so a quoted value can raiseTypeErrorat the new guard instead of being invalidated. - The diff adds no regression tests;
tests/agent/test_model_metadata.py:478-521already demonstrates the isolated on-disk cache pattern needed here.
Suggested changes
- Validate positive integer context lengths at the endpoint-metadata boundary and reject malformed cache values at the cache-hit boundary.
- Add tests for rejected writes, numeric cached-zero invalidation/fallback, and a zero returned by endpoint metadata.
Automated hermes-sweeper review.
| # edit). Without this guard, `0 is not None` short-circuits | ||
| # the resolution chain and the compressor gets context_length=0, | ||
| # breaking every status-bar and /usage display downstream. | ||
| if cached <= 0: |
There was a problem hiding this comment.
_resolve_endpoint_context_length() currently returns any integer, including 0 (agent/model_metadata.py:1060-1062), and the custom-endpoint branch returns it at :2213-2215. Please reject non-positive endpoint metadata too; otherwise a live bad response still reaches the compressor even when cache writes are refused.
Reapply the non-positive context-length guards onto the post-history-replacement mainline without carrying any stale branch history. save_context_length() now refuses to persist length <= 0 (keeping upstream's normalized _context_cache_key), and get_model_context_length() drops non-positive cache hits at the head of the invalidation chain (Codex/Kimi/MiniMax/Grok branches become elif) so a poisoned entry re-resolves instead of short-circuiting to 0. Refresh of PR NousResearch#25812; original head d62ed5eb92f057d8c707ba937b44f168f2df0677.
7d6d3a9 to
f249c4d
Compare
Reapply the non-positive context-length guards onto the post-history-replacement mainline without carrying any stale branch history. save_context_length() now refuses to persist length <= 0 (keeping upstream's normalized _context_cache_key), and get_model_context_length() drops non-positive cache hits at the head of the invalidation chain (Codex/Kimi/MiniMax/Grok branches become elif) so a poisoned entry re-resolves instead of short-circuiting to 0. Refresh of PR #25812; original head d62ed5eb92f057d8c707ba937b44f168f2df0677.
Follow-up to the salvaged #25812 — the original PR shipped without tests.
Reapply the non-positive context-length guards onto the post-history-replacement mainline without carrying any stale branch history. save_context_length() now refuses to persist length <= 0 (keeping upstream's normalized _context_cache_key), and get_model_context_length() drops non-positive cache hits at the head of the invalidation chain (Codex/Kimi/MiniMax/Grok branches become elif) so a poisoned entry re-resolves instead of short-circuiting to 0. Refresh of PR #25812; original head d62ed5eb92f057d8c707ba937b44f168f2df0677.
Follow-up to the salvaged #25812 — the original PR shipped without tests.
|
Merged via PR #85507 (merge commit d3a8be4) — your commit was cherry-picked onto current main with your authorship preserved in git log. We added regression tests on top (the never-persisted and dropped-and-re-resolved cases, sabotage-verified). Thanks for catching the |
Why
The context window display in Hermes can show a broken
? (est)orctx --value when the persistent context-length cache contains a 0 entry. This happens becauseget_model_context_length()has a guardif cached is not None— but0 is not Noneevaluates toTrue, so the function returns 0 instead of falling through to the hardcoded defaults (e.g. 1M fordeepseek-v4-pro).The zero propagates to
ContextCompressor.context_length, which then causes every downstream display code path to fail:ctx --(unknown denominator)/usage: shows pct=0% with total=0While
save_context_length()is never intentionally called with 0, a corrupted cache file (manual edit, YAML parse glitch, or a future buggy probe) can introduce a 0 entry and permanently poison the resolution chain.What changed
Two-layer defense in
agent/model_metadata.py:get_model_context_length(): After retrieving a cached value, checkcached <= 0before checking the provider-specific invalidation chain. Non-positive values are logged as a warning, invalidated from the cache, and the function falls through to normal resolution (which ends at the hardcoded 256K fallback).save_context_length(): Added a guard that refuses to persist values<= 0, logging a warning. This prevents new poison entries from ever being written.How to review
agent/model_metadata.py— the two changed functions (~40 lines of new comments/code, ~34 lines re-indented within anelse:block)else:branchEvidence
Manual testing:
Test suite:
tests/agent/test_model_metadata.py— 100 passed, 0 failed ✓tests/hermes_cli/test_custom_provider_context_length.py— all passing ✓test_compression_feasibility.py(aux compression model tests, unrelated to this change)Verification
context_length: 0in cache YAML, restart, verify warning logged and context resolves correctlyRisks / gaps
else:block re-indentation is the only structural change; the provider-specific invalidation logic (Codex, Kimi, Nous) is unchanged/collaborators @NousResearch/hermes-agent-maintainers