fix(reply): suppress JSON/channelData NO_REPLY action payloads#37904
fix(reply): suppress JSON/channelData NO_REPLY action payloads#37904kaiwangleo wants to merge 1 commit intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3dee023e04
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
| try { | ||
| const parsed = JSON.parse(trimmed) as Record<string, unknown>; | ||
| return isNoReplyActionValue(parsed.action); |
There was a problem hiding this comment.
Restrict NO_REPLY JSON suppression to action-only payloads
parseNoReplyActionJsonText marks any JSON text with action: "NO_REPLY" as silent, and the subsequent guard in normalizeReplyPayload drops the reply whenever there is no media or effective channelData. That means legitimate JSON responses such as {"action":"NO_REPLY","note":"example"} are fully suppressed even though they are not action-only control payloads, so users can receive no message when asking for JSON content that happens to include this field.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR adds suppression logic for two new forms of NO_REPLY control payloads that were leaking into user-visible Telegram messages: JSON text payloads (
Confidence Score: 2/5
Last reviewed commit: 3dee023 |
| it("keeps JSON action payload when media exists", () => { | ||
| const result = normalizeReplyPayload({ | ||
| text: '{"action":"NO_REPLY"}', | ||
| mediaUrl: "https://example.com/img.png", | ||
| }); | ||
| expect(result).not.toBeNull(); | ||
| expect(result!.mediaUrl).toBe("https://example.com/img.png"); | ||
| }); |
There was a problem hiding this comment.
Test incomplete — JSON text leaks into media payload
This test only asserts that result.mediaUrl survives, but the raw JSON string '{"action":"NO_REPLY"}' is also present in result.text and would be sent to the user.
Trace of the media-present path for text = '{"action":"NO_REPLY"}':
- Early returns at lines 72–79 are skipped because
hasMedia = true. isSilentReplyText('{"action":"NO_REPLY"}', "NO_REPLY")→false(not an exact-match).text.includes("NO_REPLY")→true, sostripSilentTokenis called.getSilentTrailingRegexproduces(?:^|\s+|\*+)NO_REPLY\s*$. In'{"action":"NO_REPLY"}'the token is preceded by", so the regex does not match and the text is returned unchanged.- The function returns
{ text: '{"action":"NO_REPLY"}', mediaUrl: '...' }.
The test should also assert that the leaked JSON is absent:
| it("keeps JSON action payload when media exists", () => { | |
| const result = normalizeReplyPayload({ | |
| text: '{"action":"NO_REPLY"}', | |
| mediaUrl: "https://example.com/img.png", | |
| }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.mediaUrl).toBe("https://example.com/img.png"); | |
| }); | |
| it("keeps JSON action payload when media exists", () => { | |
| const result = normalizeReplyPayload({ | |
| text: '{"action":"NO_REPLY"}', | |
| mediaUrl: "https://example.com/img.png", | |
| }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.mediaUrl).toBe("https://example.com/img.png"); | |
| expect(result!.text).toBe(""); | |
| }); |
Beyond the test, normalize-reply.ts also needs to clear text when textIsNoReplyActionJson is true and the function does not return early (e.g. media is present), to prevent the JSON string from reaching the user.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/reply-utils.test.ts
Line: 174-181
Comment:
**Test incomplete — JSON text leaks into media payload**
This test only asserts that `result.mediaUrl` survives, but the raw JSON string `'{"action":"NO_REPLY"}'` is also present in `result.text` and would be sent to the user.
Trace of the media-present path for `text = '{"action":"NO_REPLY"}'`:
1. Early returns at lines 72–79 are skipped because `hasMedia = true`.
2. `isSilentReplyText('{"action":"NO_REPLY"}', "NO_REPLY")` → `false` (not an exact-match).
3. `text.includes("NO_REPLY")` → `true`, so `stripSilentToken` is called.
4. `getSilentTrailingRegex` produces `(?:^|\s+|\*+)NO_REPLY\s*$`. In `'{"action":"NO_REPLY"}'` the token is preceded by `"`, so the regex **does not match** and the text is returned unchanged.
5. The function returns `{ text: '{"action":"NO_REPLY"}', mediaUrl: '...' }`.
The test should also assert that the leaked JSON is absent:
```suggestion
it("keeps JSON action payload when media exists", () => {
const result = normalizeReplyPayload({
text: '{"action":"NO_REPLY"}',
mediaUrl: "https://example.com/img.png",
});
expect(result).not.toBeNull();
expect(result!.mediaUrl).toBe("https://example.com/img.png");
expect(result!.text).toBe("");
});
```
Beyond the test, `normalize-reply.ts` also needs to clear `text` when `textIsNoReplyActionJson` is true and the function does not return early (e.g. media is present), to prevent the JSON string from reaching the user.
How can I resolve this? If you propose a fix, please make it concise.| function isNoReplyActionValue(value: unknown): boolean { | ||
| return typeof value === "string" && value.trim().toUpperCase() === SILENT_REPLY_TOKEN; |
There was a problem hiding this comment.
opts.silentToken ignored in new helper functions
isNoReplyActionValue (and therefore parseNoReplyActionJsonText and containsNoReplyAction) always compares against the hardcoded SILENT_REPLY_TOKEN constant. However, normalizeReplyPayload supports a configurable silentToken via opts.silentToken ?? SILENT_REPLY_TOKEN (resolved at line 86). Any caller that passes a custom silentToken will find the new JSON and channelData suppression paths silently ignore their override — inconsistent with how all the pre-existing token checks in this function behave.
The helpers should accept the effective token as a parameter, and normalizeReplyPayload should resolve opts.silentToken ?? SILENT_REPLY_TOKEN before the new early-return checks and thread it through every call site.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/normalize-reply.ts
Line: 28-29
Comment:
**`opts.silentToken` ignored in new helper functions**
`isNoReplyActionValue` (and therefore `parseNoReplyActionJsonText` and `containsNoReplyAction`) always compares against the hardcoded `SILENT_REPLY_TOKEN` constant. However, `normalizeReplyPayload` supports a configurable `silentToken` via `opts.silentToken ?? SILENT_REPLY_TOKEN` (resolved at line 86). Any caller that passes a custom `silentToken` will find the new JSON and `channelData` suppression paths silently ignore their override — inconsistent with how all the pre-existing token checks in this function behave.
The helpers should accept the effective token as a parameter, and `normalizeReplyPayload` should resolve `opts.silentToken ?? SILENT_REPLY_TOKEN` **before** the new early-return checks and thread it through every call site.
How can I resolve this? If you propose a fix, please make it concise.
Summary
{"action":"NO_REPLY"}as silent internal control payloadsaction: NO_REPLYWhy
Issue #37727 reports occasional leakage of internal NO_REPLY action payloads into Telegram user-visible messages.
Validation
Fixes #37727