Skip to content

fix: stop swallowing failed chat responses as empty success#5

Open
yhurynovich wants to merge 4 commits into
ForgetMeAI:mainfrom
yhurynovich:main
Open

fix: stop swallowing failed chat responses as empty success#5
yhurynovich wants to merge 4 commits into
ForgetMeAI:mainfrom
yhurynovich:main

Conversation

@yhurynovich

Copy link
Copy Markdown

fix: stop swallowing failed chat responses as empty success

The generic error fallback in handleApiError() could produce an
empty-string error field when neither response.error nor
response.statusText were set (common with Node's undici fetch on
plain non-2xx responses). Every if (result.error) check across
routes.js treats an empty string as falsy, so real upstream
failures (e.g. Qwen's "Model not found") were silently returned
to clients as a 200 with empty content and zeroed usage instead
of a proper error.

  • Guarantee handleApiError's fallback error is always a non-empty
    string, falling back to HTTP <status> when no better message
    is available
  • Bump error-body logging from debug to warn so Qwen's raw error
    response is visible in logs without changing LOG_LEVEL
  • Add DEBUG_SSE_CHUNKS-gated raw SSE chunk logging for future
    streaming diagnostics

The generic error fallback in handleApiError() could produce an
empty-string `error` field when neither response.error nor
response.statusText were set (common with Node's undici fetch on
plain non-2xx responses). Every `if (result.error)` check across
routes.js treats an empty string as falsy, so real upstream
failures (e.g. Qwen's "Model not found") were silently returned
to clients as a 200 with empty content and zeroed usage instead
of a proper error.

- Guarantee handleApiError's fallback error is always a non-empty
  string, falling back to `HTTP <status>` when no better message
  is available
- Bump error-body logging from debug to warn so Qwen's raw error
  response is visible in logs without changing LOG_LEVEL
- Add DEBUG_SSE_CHUNKS-gated raw SSE chunk logging for future
  streaming diagnostics
Problem

When Qwen returns an HTTP 429 (rate limit), the account gets cooled down via markRateLimitedByToken(). This cooldown was always defaulting to 24 hours, even when Qwen's own response says the wait is much shorter (e.g. 33 minutes).

Root cause

Two call sites in src/api/chat.js parse the 429 error body to get the wait time:

js
const rateInfo = JSON.parse(response.errorBody);
const parsedHours = Number(rateInfo.num);

Qwen's actual error body nests the wait duration under data.num, not top-level num:

json
{
  "success": false,
  "data": {
    "code": "RateLimited",
    "template": "You have reached the daily usage limit. Please wait {{num}} minutes before trying again.",
    "num": 33
  }
}

Since rateInfo.num is always undefined, Number.isFinite() fails and the code silently falls back to the hardcoded 24-hour default — ~40x longer than the 33 minutes Qwen actually requested. On top of the wrong path, the value is also documented by Qwen's own template string as minutes, while the code treats it as hours.

Fix
Read the wait value from data.num instead of top-level num.
Convert minutes to hours (/ 60) before passing to markRateLimitedByToken(), since that function multiplies by 3600 * 1000.
Falls back to 24h if the field is missing/non-numeric, preserving prior safe behavior.
Applied identically at both call sites (v2 sendMessage flow and the legacy v1 flow).
Impact

Accounts hitting Qwen's rate limit now cool down for the actual duration Qwen specifies instead of a flat 24h — significant for single-account deployments, where an inflated cooldown means total downtime.

Testing
node --check src/api/chat.js passes
Verified against a captured 429 response body (data.num: 33, template confirms "minutes")
Confirmed no other call sites read .num (grep -n "\.num\b" src/api/*.js)
Note

Only one 429 sample was available to confirm the "minutes" unit via data.template. If a RateLimited response is ever seen with a different template/unit, this assumption should be revisited.
Behavior:
- When a user sends {"message": "/new"} to /api/chat or /api/chat/completions, the browser session is restarted
- The message is NOT forwarded to the Qwen model
- Returns success/failure JSON response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant