Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions gateway/platforms/weixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2106,11 +2106,35 @@ async def send_weixin_direct(
adapter._token_store = token_store

last_result: Optional[SendResult] = None
cleaned = adapter.format_message(message)
if cleaned:
last_result = await adapter.send(chat_id, cleaned)
if not last_result.success:
return {"error": f"Weixin send failed: {last_result.error}"}

async def _try_send_with_refresh() -> Optional[SendResult]:
nonlocal last_result
cleaned = adapter.format_message(message)
if cleaned:
last_result = await adapter.send(chat_id, cleaned)
return last_result

# Retry once on session/rate-limit errors, then give an
# actionable error so cron/send_message can surface it.
for _retry in range(2):
last_result = await _try_send_with_refresh()
if last_result and last_result.success:
break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This only recognizes an error string containing session, but the existing adapter formats an unhandled -14 response as iLink sendmessage error: ret=... errcode=-14 errmsg=.... Inspect the structured response or explicitly recognize errcode=-14; otherwise the claimed session-error retry can be skipped.

err = last_result.error if last_result else ""
is_rate = "rate limited" in err or "ret=-2" in err
is_session = "session" in err.lower()
if not is_rate and not is_session:
break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This removes only the in-memory cache entry. ContextTokenStore.restore() reloads the account token file after restart, and this path does not call persistence, so the stale token described in the PR will return on the next process start.

# Clear stale context_token and retry once without it
if context_token:
token_store._cache.pop(
token_store._key(account_id, chat_id), None
)
context_token = None
await asyncio.sleep(3.0)

if not last_result or not last_result.success:
return {"error": f"Weixin send failed: {last_result.error if last_result else 'unknown'}"}

for media_path, _is_voice in media_files or []:
ext = Path(media_path).suffix.lower()
Expand Down