When nanobot is run as a systemd service with ProtectSystem=strict,
the process cwd defaults to the read-only root filesystem (/). botpy's
default Client configuration includes a TimedRotatingFileHandler that
writes 'botpy.log' to os.getcwd(), which raises [Errno 30] Read-only
file system.
Pass ext_handlers=False when constructing the botpy Client subclass to
suppress the file handler. nanobot already routes all log output through
loguru, so botpy's file handler is redundant.
Fixes#1343
Co-Authored-By: Claude <noreply@anthropic.com>
Prevent infinite loops by tracking processed message IDs in WhatsApp
channel. The bridge may send duplicate messages which caused the bot
to respond repeatedly with the same generic message.
Changes:
- Add _processed_message_ids deque (max 2000) to track seen messages
- Skip processing if message_id was already processed
- Align WhatsApp dedup with other channels (Feishu, Email, Mochat, QQ)
This fixes the issue where WhatsApp gets stuck in a loop sending
identical responses repeatedly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bare keyword "codex" causes false positive matches when any model
name happens to contain "codex" (e.g. "gpt-5.3-codex" on a custom
provider). This incorrectly routes the request through the OAuth-based
OpenAI Codex provider, producing "OAuth credentials not found" errors
even when a valid custom api_key and api_base are configured.
Keep only the explicit "openai-codex" keyword so that auto-detection
requires the canonical prefix. Users can still set provider: "custom"
to force the custom endpoint, but auto-detection should not collide.
Closes#1311
When an LLM returns content: null on a plain assistant message (no
tool_calls), the null gets saved to session history and causes
permanent 400 errors on every subsequent request.
- Sanitize None content on plain assistant messages to "(empty)" in
_sanitize_empty_content(), matching the existing empty-string handling
- Skip persisting error responses (finish_reason="error") to the
message history in _run_agent_loop(), preventing poison loops
Closes#1303
QQ's bot API requires a msg_id (original inbound message ID) to send a
passive reply. Without it the request is treated as a proactive message
and fails with error 40034102 (无权限). The message_id was already stored
in InboundMessage.metadata and forwarded to OutboundMessage, but was never
read in send().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Some models (e.g., Kimi K2.5 via OpenRouter) return tool call arguments
as a list instead of a dict. This caused an AttributeError when trying
to call .values() on the list.
The fix checks if arguments is a list and extracts the first element
before accessing .values().
Made-with: Cursor