Conversation
| // Ack reaction (👀) on any inbound message we accept. | ||
| try { | ||
| await reactEyes(client, message.id); | ||
| } catch { | ||
| // Best-effort; ignore reaction failures. | ||
| } |
There was a problem hiding this comment.
[P1] Reaction ack is applied before access-control gating
reactEyes() is executed for every inbound message right after basic validation (sender_email/self/text), before DM policy / allowlist / mention-gating checks. This means even messages that are later dropped (e.g. stream not mentioned, dmPolicy=disabled, sender not allowlisted) will still get an 👀 reaction, which is observable and can unintentionally “acknowledge” untrusted senders.
Consider moving the reaction call to after the message has passed the relevant policy gates (or gating it to the same conditions as reply/accept).
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zulip/src/zulip/monitor.ts
Line: 169:174
Comment:
[P1] Reaction ack is applied before access-control gating
`reactEyes()` is executed for every inbound message right after basic validation (sender_email/self/text), before DM policy / allowlist / mention-gating checks. This means even messages that are later dropped (e.g. stream not mentioned, dmPolicy=disabled, sender not allowlisted) will still get an 👀 reaction, which is observable and can unintentionally “acknowledge” untrusted senders.
Consider moving the reaction call to after the message has passed the relevant policy gates (or gating it to the same conditions as reply/accept).
How can I resolve this? If you propose a fix, please make it concise.| const useAccessGroups = (cfg as any).commands?.useAccessGroups !== false; | ||
| const commandGate = resolveControlCommandGate({ | ||
| useAccessGroups, | ||
| authorizers: [{ configured: effectiveAllowFrom.length > 0, allowed: senderAllowedDm }], | ||
| allowTextCommands, | ||
| hasControlCommand, | ||
| }); | ||
| const commandAuthorized = isDm ? (dmPolicy === "open" || senderAllowedDm) : commandGate.commandAuthorized; | ||
|
|
There was a problem hiding this comment.
[P0] Stream control command gate uses DM allowlist decision
For stream messages, resolveControlCommandGate() is given allowed: senderAllowedDm (computed from effectiveAllowFrom). If groupAllowFrom is configured (or if stream allow rules differ from DM allow rules), a sender who is allowed in a stream can still be treated as not-authorized for control commands because the gate is using the DM allow decision.
This shows up when groupAllowFrom is non-empty (or group policy differs): the code passes senderAllowedGroup for stream gating earlier, but command authorization still keys off senderAllowedDm.
Also appears to affect: commandAuthorized for group messages (commandGate.commandAuthorized depends on that authorizer input).
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zulip/src/zulip/monitor.ts
Line: 226:234
Comment:
[P0] Stream control command gate uses DM allowlist decision
For stream messages, `resolveControlCommandGate()` is given `allowed: senderAllowedDm` (computed from `effectiveAllowFrom`). If `groupAllowFrom` is configured (or if stream allow rules differ from DM allow rules), a sender who is allowed in a stream can still be treated as not-authorized for control commands because the gate is using the DM allow decision.
This shows up when `groupAllowFrom` is non-empty (or group policy differs): the code passes `senderAllowedGroup` for stream gating earlier, but command authorization still keys off `senderAllowedDm`.
Also appears to affect: `commandAuthorized` for group messages (`commandGate.commandAuthorized` depends on that authorizer input).
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| try { | ||
| await core.channel.reply.dispatchReplyFromConfig({ | ||
| ctx: ctxPayload, | ||
| cfg, | ||
| dispatcher, | ||
| replyOptions: { | ||
| ...replyOptions, | ||
| onModelSelected: prefixContext.onModelSelected, | ||
| }, | ||
| }); | ||
| } finally { |
There was a problem hiding this comment.
[P2] Indentation error in try/finally block
The try { await core.channel.reply.dispatchReplyFromConfig(...) } finally { ... } block is mis-indented (the await and closing braces are not indented under try). It still parses, but it’s easy to misread and looks like a formatting regression.
Suggestion: re-indent the block so the await is nested under try { and the closing braces align.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zulip/src/zulip/monitor.ts
Line: 349:360
Comment:
[P2] Indentation error in try/finally block
The `try { await core.channel.reply.dispatchReplyFromConfig(...) } finally { ... }` block is mis-indented (the `await` and closing braces are not indented under `try`). It still parses, but it’s easy to misread and looks like a formatting regression.
Suggestion: re-indent the block so the `await` is nested under `try {` and the closing braces align.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
The docs and hint string specify If you intend to support only Prompt To Fix With AIThis is a comment left during a code review.
Path: extensions/zulip/src/normalize.ts
Line: 664:668
Comment:
[P1] `stream:` target parsing accepts `#`/`:` separators in addition to `/`
The docs and hint string specify `stream:<stream>/<topic>`, but parsing uses `rest.split(/\s*[/#:]\s*/, 2)`, which will treat `stream:Engineering#Alerts` and `stream:Engineering:Alerts` as valid too. If `:` is part of a stream name (or if you want to reserve `#` for the shorthand form only), this can produce surprising routing.
If you intend to support only `/` for the explicit `stream:` form, consider tightening the split regex to just `/` (and keep `#` handling in the separate shorthand branch).
How can I resolve this? If you propose a fix, please make it concise. |
|
Pushed fixes:
Commits:
Thanks for the review. |
|
Pushed fixes (non-JSON diagnostics):
Commit: 23da029 |
|
Pushed fixes (non-JSON diagnostics):
Commit: 23da029 |
|
Pushed fixes (Windows/alt-deploy diagnostics):
Commit: ddb991d |
|
Pushed fixes (retry behavior aligned w/ other channel monitors):
Commit: adabeca |
|
Pushed improvements (mirroring existing channel plugin patterns):
Commit: 78c497d |
|
Pushed fixes (Zulip uploads/media access):
Commit: 47e9567 |
Adds a full Zulip channel plugin with: - Event queue polling with exponential backoff - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Refs: #5163, openclaw#8365, openclaw#9643
Adds a full Zulip channel plugin with: - Event queue polling with exponential backoff - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Refs: #5163, openclaw#8365, openclaw#9643
|
Pushed updates to this PR branch with the Zulip finish-up work requested. What was implemented
Tests added/updated
Validation run
Commit pushed:
|
|
Pushed additional fixes to the PR head branch ( Included changes (from PR #9643 best ideas + reliability improvements)
Validation
Head branch update:
|
|
Closing in favor of PR #12183, which supersedes this work.\n\nSuperseding PR: https://github.com/openclaw/openclaw/pull/12183\n\nThis PR implemented Zulip plugin improvements (multi-base-url failover, topic isolation, monitor backoff, etc.), but the upstreamed implementation in #12183 is now the recommended path. |
Adds a full Zulip channel plugin with: - Event queue polling with exponential backoff - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Refs: #5163, openclaw#8365, openclaw#9643
Adds a full-featured Zulip channel plugin with: Core Features: - Event queue polling with exponential backoff and auto-recovery - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Key Improvement Over Existing PRs: - Implements CONCURRENT message processing with staggered start times - Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing reply delays and "message dumps" when multiple messages arrive - This implementation starts processing each message immediately with a 200ms stagger for natural conversation flow - Replies arrive as each finishes instead of all at once Technical Details: - Fire-and-forget message processing with per-message error handling - Maintains event cursor and queue ID correctly during concurrent processing - Tested live and confirmed significant UX improvement Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
Adds a full-featured Zulip channel plugin with: Core Features: - Event queue polling with exponential backoff and auto-recovery - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Key Improvement Over Existing PRs: - Implements CONCURRENT message processing with staggered start times - Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing reply delays and "message dumps" when multiple messages arrive - This implementation starts processing each message immediately with a 200ms stagger for natural conversation flow - Replies arrive as each finishes instead of all at once Technical Details: - Fire-and-forget message processing with per-message error handling - Maintains event cursor and queue ID correctly during concurrent processing - Tested live and confirmed significant UX improvement Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
Adds a full-featured Zulip channel plugin with: Core Features: - Event queue polling with exponential backoff and auto-recovery - Authenticated upload downloads from /user_uploads/ - Outbound file uploads via /api/v1/user_uploads - Reaction indicators (eyes → check_mark/warning) - Topic directive [[zulip_topic: <topic>]] - HTTP retry with backoff (429/502/503/504) - Full actions API (stream CRUD, user management, reactions) - Channel docs and unit tests Key Improvement Over Existing PRs: - Implements CONCURRENT message processing with staggered start times - Other PRs (openclaw#9643, openclaw#14182) process messages sequentially, causing reply delays and "message dumps" when multiple messages arrive - This implementation starts processing each message immediately with a 200ms stagger for natural conversation flow - Replies arrive as each finishes instead of all at once Technical Details: - Fire-and-forget message processing with per-message error handling - Maintains event cursor and queue ID correctly during concurrent processing - Tested live and confirmed significant UX improvement Refs: openclaw#5163, openclaw#8365, openclaw#9643, openclaw#12183
Adds an official Zulip channel plugin.
Discussion / Feature request: https://github.com/openclaw/openclaw/discussions/5163
Key features:
Docs:
Tests:
Security defaults:
Greptile Overview
Greptile Summary
This PR adds an official Zulip channel plugin under
extensions/zulip/, including config schema + account resolution, target normalization/parsing, a Zulip HTTP client (with optional Cloudflare Access service-token headers), an event monitor loop using/register+/events, and outbound send support for streams/topics and DMs (resolving recipient emails to numericuser_idvia/api/v1/users).It also updates channel docs to include Zulip and adds unit tests covering target parsing, user-id resolution (including
delivery_email), Cloudflare HTML detection, typing/reaction payload shapes, and DM send behavior. There’s a small core change inagent-runner-memoryaround compaction/flush counting plus added regression coverage.Main issues found are around Zulip monitor gating: reactions are currently sent before access-control checks (so dropped messages still get acknowledged), and stream control-command authorization appears to use the DM allowlist decision rather than the group allowlist decision (mis-authorizing commands when
groupAllowFromdiffers).Confidence Score: 3/5
groupAllowFrom). Fixing those reduces behavioral/security surprises.Context used:
dashboard- CLAUDE.md (source)dashboard- AGENTS.md (source)