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>
- Remove trailing whitespace and normalize blank lines
- Unify string quotes and line breaks for long lines
- Sort imports alphabetically across modules
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>