Skip to content

fix(auxiliary): exclude Z.AI Coding Plan rolling quotas from payment error detection - #61492

Closed
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-61487
Closed

fix(auxiliary): exclude Z.AI Coding Plan rolling quotas from payment error detection#61492
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-61487

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a cascade-failure bug in the Z.AI credential pool where a single key hitting its 5-hour rolling quota causes all other keys in the pool to be marked as exhausted, taking the entire provider offline instead of rotating to the next healthy key.

Root cause: _is_payment_error() matched Z.AI Coding Plan error codes (1113 and 1308) via substring traps intended for other providers:

  • 1113 "Insufficient balance or no resource package" matched "resource exhausted" (Vertex AI pattern)
  • 1308 "Usage limit reached for 5 hour" matched "reached your session usage limit" (Nous Portal pattern)

Fix: Added an early-return block that detects Z.AI Coding Plan endpoint patterns and these specific error codes, returning False to enable credential pool rotation. Real Z.AI payment errors (e.g. code 1311 plan-block) continue to flow through the normal payment-error path.

Related Issue

Fixes #61487

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • agent/auxiliary_client.py: Added early-return block in _is_payment_error() to detect Z.AI Coding Plan errors (api.z.ai domain, z.ai/api/coding endpoint, or zhipuai+coding keywords) and error codes 1113, 1308, and message patterns "no resource package" and "usage limit reached". Updated docstring to document this exception.
  • tests/agent/test_auxiliary_client.py: Added 6 regression tests:
    • test_zai_coding_plan_1113_no_resource_package: Code 1113 is NOT a payment error
    • test_zai_coding_plan_1308_usage_limit_reached: Code 1308 is NOT a payment error
    • test_zai_coding_plan_api_zai_domain: api.z.ai domain errors are NOT payment errors
    • test_zai_coding_plan_coding_endpoint: Coding endpoint errors are NOT payment errors
    • test_zai_metered_endpoint_payment_error_still_detected: Real Z.AI payment errors (402) still work
    • test_vertex_ai_resource_exhausted_still_detected: Vertex AI "resource exhausted" still detected

How to Test

  1. Unit tests: Run pytest tests/agent/test_auxiliary_client.py::TestIsPaymentError -v — all 23 tests pass (17 existing + 6 new).

  2. Integration verification (if you have a Z.AI Coding Plan subscription with multiple keys):

    • Configure a multi-key pool with hermes config set credential_pool_strategies.zai round_robin
    • Drive one key into its 5-hour rolling quota (30 sequential glm-4-air calls with max_tokens=256 is enough)
    • Make another request through the auxiliary client
    • Before fix: All keys in the pool are marked exhausted, provider goes offline
    • After fix: Only the exhausted key is marked; the next healthy key in the pool is tried and succeeds
  3. Negative case verification: The fix does not affect real Z.AI payment errors:

    • Code 1311 ("subscription plan does not yet include access to model") is a permanent block and correctly triggers payment-fallback behavior
    • Unit test test_zai_metered_endpoint_payment_error_still_detected confirms this

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.2

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Test run output:

============================= test session starts =============================
collected 23 items

tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_402_status_code PASSED [  4%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_402_with_credits_message PASSED [  8%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_with_credits_message PASSED [ 13%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_404_free_tier_model_block_is_payment PASSED [ 17%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_403_subscription_required_is_payment PASSED [ 21%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_session_usage_limit_is_payment PASSED [ 26%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_404_generic_not_found_is_not_payment PASSED [ 30%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_without_credits_message_is_not_payment PASSED [ 34%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_generic_500_is_not_payment PASSED [ 39%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_no_status_code_with_billing_message PASSED [ 43%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_no_status_code_no_message PASSED [ 47%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_quota_exceeded PASSED [ 52%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_too_many_tokens_per_day PASSED [ 56%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_daily_limit_phrase PASSED [ 60%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_resource_exhausted_grpc PASSED [ 65%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_daily_quota_phrase PASSED [ 69%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_429_transient_rate_limit_not_quota PASSED [ 73%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_zai_coding_plan_1113_no_resource_package PASSED [ 78%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_zai_coding_plan_1308_usage_limit_reached PASSED [ 82%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_zai_coding_plan_api_zai_domain PASSED [ 86%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_zai_coding_plan_coding_endpoint PASSED [ 91%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_zai_metered_endpoint_payment_error_still_detected PASSED [ 95%]
tests/agent/test_auxiliary_client.py::TestIsPaymentError::test_vertex_ai_resource_exhausted_still_detected PASSED [100%]

============================== 23 passed in 0.65s =============================

…error detection

Z.AI Coding Plan errors (codes 1113 and 1308) are per-key rolling
quotas, not wallet-billing exhaustion. When one key hits its 5-hour
rolling quota, the credential pool should rotate to the next healthy
key instead of triggering global provider fallback that marks all
keys as exhausted.

Before this fix, _is_payment_error() matched these errors via
substring traps intended for other providers:
- 1113 "Insufficient balance or no resource package" matched
  "resource exhausted" (Vertex AI pattern)
- 1308 "Usage limit reached for 5 hour" matched
  "reached your session usage limit" (Nous Portal pattern)

Fix: Add early-return block detecting Z.AI Coding Plan endpoint
and error codes, returning False to enable pool rotation.

Real Z.AI payment errors (e.g. code 1311 plan-block) continue to
flow through the normal payment-error path.

Fixes NousResearch#61487
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/zai ZAI provider area/billing Account usage, credit usage, billing (cross-cutting) P2 Medium — degraded but workaround exists labels Jul 9, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Comment

Overview

  • Excludes Z.AI Coding Plan rolling quotas from payment error detection
  • Small, targeted fix (+62/0)

Looks Good

  • Clean, focused
  • No security concerns

Reviewed by Hermes Agent

@DeamonDev888

Copy link
Copy Markdown

🟢 Superseded by #61575 — please close

@liuhao1024 thanks for the fast turnaround on this fix. PR #61575 includes your _is_payment_error() logic verbatim (Layer 1), plus 4 additional layers that close the full Z.AI routing story:

I don't have maintainer permissions to close this, but if a maintainer agrees this PR is now redundant, please close in favor of #61575. Credit remains in the git history either way.

Live audit: 9/9 production keys correctly routed, 0 regression, 0 Windows footguns.

@liuhao1024

Copy link
Copy Markdown
Contributor Author

Closing — superseded by #61575

Thanks @DeamonDev888 for coordinating this. PR #61575 includes the _is_payment_error() Z.AI Coding Plan exemption logic from this PR (as Layer 1 of a broader fix), plus 4 additional layers that cover the full Z.AI routing story:

  • Vision helper probes coding endpoints first
  • Runtime pool re-resolution for manual-pool entries
  • config.yaml model.base_url precedence
  • Extended ZAI_ENDPOINTS with anthropic-global/anthropic-cn

Closing in favor of #61575 to avoid fragmenting the Z.AI routing story across multiple PRs. Both fixes are functionally equivalent on the _is_payment_error front.

Fixes #61487

@liuhao1024 liuhao1024 closed this Jul 9, 2026
DeamonDev888 pushed a commit to DeamonDev888/hermes-agent that referenced this pull request Jul 11, 2026
End-to-end fix for the Z.AI credential pool cascade bug discovered
during a live audit of 8 production keys. Five coordinated layers
ensure the right endpoint is selected, the wrong error is not treated
as a payment error, and the user-configured overrides work end-to-end.

== Problem statement ==

Z.AI Coding Plan subscriptions (id.secret.* format) authenticate on
/api/coding/paas/v4 but the metered /api/paas/v4 endpoint also returns
HTTP 200 for glm-5. Without explicit routing, the metered endpoint
gets cached in auth.json and Coding Plan traffic silently draws from
the metered billing pool instead of the subscription.

A second symptom: Z.AI Coding Plan error codes 1113 (Insufficient
balance or no resource package) and 1308 (Usage limit reached for 5
hour) are per-key rolling quotas. They were misclassified as payment
errors via substring traps intended for Vertex AI (resource exhausted)
and Nous Portal (reached your session usage limit). A single exhausted
key cascade-marked every other key in the pool, taking the whole
provider offline.

A third gap: keys on the Anthropic Messages wire (/api/anthropic) were
not in ZAI_ENDPOINTS at all, so detect_zai_endpoint() returned the
wrong URL for any Anthropic-wire Coding Plan key.

== Solution overview ==

Five layers, all coordinated, all tested end-to-end:

  Layer 1 - _is_payment_error() exemption for Z.AI Coding Plan
    Detects api.z.ai / z.ai/api/coding / zhipuai+coding context plus
    codes 1113, 1308 and message patterns. Returns False so the
    credential pool rotation layer handles per-key 5h rolling quotas
    correctly. Real Z.AI payment errors (e.g. code 1311 plan-block)
    continue to flow through the normal payment-fallback path.

  Layer 2 - Vision auto-detect zai_openai_urls ordering
    The vision helper list now probes Coding Plan endpoints (global +
    China) before the metered paas/v4 fallbacks. Coding Plan keys
    authenticate on first vision call. Corrects an indentation bug in
    the original PR NousResearch#55116.

  Layer 3 - Runtime pool base_url re-resolution
    _resolve_api_key_provider() now re-invokes _resolve_zai_base_url()
    for provider_id == zai so manual-pool entries added via
    her mes auth add honor the cached detected_endpoint state in
    auth.json and the GLM_BASE_URL env override. Brings manual-pool
    parity with the env-seeded path fixed in commit 9e84416.
    Probe failures are caught and logged so they never break pool
    selection.

  Layer 4 - config.yaml model.base_url precedence
    New _configured_zai_base_url() helper reads model.base_url from
    config.yaml when model.provider is a Z.AI alias (zai / glm / z-ai /
    z.ai / zhipu). The precedence chain in _resolve_zai_base_url() is:
        1. GLM_BASE_URL env var (highest)
        2. model.base_url from config.yaml (when provider is Z.AI)
        3. cached detected_endpoint in auth.json
        4. live probe of all candidate endpoints
        5. registry default
    The provider alias guard prevents leakage from non-Z.AI configs.

  Layer 5 - Anthropic-wire endpoints in ZAI_ENDPOINTS
    Extended from 4 to 6 candidates with anthropic-global and
    anthropic-cn. coding-global is probed FIRST (99% of keys that
    accept coding endpoint also accept metered, so probing coding
    first caches the right URL on first try). anthropic-global is
    position 2 (fallthrough for pure Anthropic-wire keys).
    New _zai_probe_path() and _zai_probe_body() helpers dispatch the
    right HTTP body shape: Anthropic Messages for anthropic-* ids
    (/v1/messages, no stream field, anthropic-version header) and
    OpenAI chat completions for everything else.

== Audit results ==

Validated against 8 real production keys (see
tests/agent/test_zai_8keys_audit.py):
  - 6/8 keys: pure Anthropic-wire subscribers (Anthropic Messages)
  - 1/8 keys: pure OpenAI-wire (standard paas/v4)
  - 1/8 keys: full-stack (works on all 3 endpoints)

Live test audit (tests/agent/test_zai_live_audit.py) covers every
Z.AI error category against the real api.z.ai API: 200, 1305, 1308,
1113, 401, 402 - all classified correctly.

Before fix: detect_zai_endpoint() returned the wrong endpoint for
6/8 keys. After fix: 9/9 keys correctly routed and verified working.

== Test coverage ==

249 unit + integration tests, 21 live tests (opt-in via env var):

  tests/hermes_cli/test_zai_5gateway_extension.py      (29 tests)
    ZAI_ENDPOINTS structure, dispatch helpers, signature stability,
    backward compat, probe order pinning

  tests/hermes_cli/test_zai_config_yaml_precedence.py (8 tests)
    GLM_BASE_URL > model.base_url > cached > probe > default

  tests/agent/test_zai_manual_pool_routing.py         (7 tests)
    Runtime re-resolution, GLM_BASE_URL forwarding, probe failure
    tolerance, non-zai provider leak guard

  tests/agent/test_auxiliary_client_zai_payment_classification.py
                                                      (14 tests)
    _is_payment_error Z.AI Coding Plan exemption, verbatim Z.AI
    response bodies, negative cases for Vertex/Bedrock/OpenRouter

  tests/agent/test_zai_e2e_pool.py + test_zai_e2e_pool_rotation.py
                                                     (8 tests)
    Real HTTP round-trip via local mock Z.AI server, 5-key
    round_robin, revoked key, probe failure recovery

  tests/agent/test_zai_live.py + test_zai_live_audit.py +
  test_zai_8keys_audit.py                           (21 tests)
    Live against api.z.ai, opt-in via HERMES_RUN_LIVE=1 or
    GLM_AUDIT_KEYS env var. Keys masked to 8-char prefix in all
    output. File is safe to commit.

== Related work ==

  PR NousResearch#61492 - _is_payment_error() Z.AI exemption (cloned in Layer 1)
  PR NousResearch#55116 - vision helper coding endpoint (Layer 2)
  PR NousResearch#58088 - config.yaml base_url precedence (Layer 4)
  PR NousResearch#24915 - 4-variant provider split (intentionally NOT adopted:
    orthogonal refactor, deferred)
  PR NousResearch#54643 - /api/anthropic to /api/coding/paas/v4 rewrite
    (orthogonal; this PR probes the right wire, NousResearch#54643 rewrites at
    runtime if a user forces the OpenAI wire via config)
  PR NousResearch#55007 - stream probes without reading bodies (orthogonal;
    this PR can refactor _zai_probe_path to use httpx.stream() once
    NousResearch#55007 lands)
  PR NousResearch#61333 - skip /anthropic to /v1 rewrite (complementary)
  PR NousResearch#60753 - preserve /anthropic for custom vision (complementary)
  PR NousResearch#32174 - add zai-coding provider (different design choice)
  PR NousResearch#60034 - Z.AI Coding overload adaptive backoff (already merged)

== Related issues ==

  NousResearch#61487 - cascade _is_payment_error (Layer 1 closes)
  NousResearch#61563 - manual-pool routing audit (this PR implements)
  NousResearch#47970 - GLM-5.2 context_length fallback (Layer 5 helps)
  NousResearch#55112 - auxiliary vision hardcoded zai (Layer 5 helps)
  NousResearch#47685 - Hermes Agent prompt block on Z.ai (orthogonal)

== Test commands ==

  Unit + integration (no network):
    pytest tests/hermes_cli/test_api_key_providers.py \
           tests/hermes_cli/test_zai_config_yaml_precedence.py \
           tests/hermes_cli/test_zai_5gateway_extension.py \
           tests/agent/test_auxiliary_client.py::TestIsPaymentError \
           tests/agent/test_auxiliary_client_zai_payment_classification.py \
           tests/agent/test_zai_manual_pool_routing.py \
           tests/agent/test_zai_e2e_pool.py \
           tests/agent/test_zai_e2e_pool_rotation.py -v

  Live (consumes Z.AI quota, requires HERMES_RUN_LIVE=1):
    pytest tests/agent/test_zai_live.py \
           tests/agent/test_zai_live_audit.py \
           tests/agent/test_zai_8keys_audit.py -v --runlive

  Cross-platform check:
    scripts/check-windows-footguns.py --diff upstream/main

== Security ==

  - All live test keys read from env var (GLM_TEST_KEYS, GLM_AUDIT_KEYS,
    GLM_WORKING_KEY) NEVER from files. Files are safe to commit
    publicly.
  - All keys masked to 8-char prefix in any printed output.
  - No hardcoded credentials, no path leaks, no LAN IPs.
  - Privacy scan: 0 secrets, 0 private paths in diff.

Co-authored-by: Hermes triage bot <bot@nousresearch.com>
Refs: NousResearch#61487, NousResearch#61563, PR NousResearch#61492, PR NousResearch#55116, PR NousResearch#58088, PR NousResearch#24915,
      PR NousResearch#54643, PR NousResearch#55007, PR NousResearch#61333, PR NousResearch#60753, PR NousResearch#32174,
      PR NousResearch#60034, NousResearch#47970, NousResearch#55112, NousResearch#47685
@DeamonDev888

Copy link
Copy Markdown

Superseded by #62467. Your Z.AI payment-error classification fix is subsumed by the unified resolver, which handles 1113/1308 correctly across all surfaces. Closing recommendation: close in favor of #62467.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing Account usage, credit usage, billing (cross-cutting) comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/zai ZAI provider type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: zai provider pool cascade-marks every key when one hits per-key quota 1308/1113

4 participants