#23184: fix(auto-reply): prevent reminder guard note from leaking into channel messages
size: XS
experienced-contributor
Cluster:
Error Payload Filtering
## Summary
- **Bug**: Internal reminder guardrail note leaks into user-visible Telegram/channel messages
- **Root cause**: `appendUnscheduledReminderNote()` in `agent-runner.ts` appends the note directly to `payload.text`, which flows to channel delivery
- **Fix**: Move the note to a new `systemNote` field on `ReplyPayload` that channels don't render
Fixes #23124
## Problem
When the agent makes a reminder commitment (e.g. "I'll remind you tomorrow morning") but doesn't actually schedule a cron job, the system appends an internal guardrail note:
> Note: I did not schedule a reminder in this turn, so this will not trigger automatically.
This note was appended directly to `payload.text` in `appendUnscheduledReminderNote()` at `agent-runner.ts:87-90`. Since all channel delivery code (Telegram, Slack, Discord, etc.) reads `payload.text` to send messages, the note was visible to end users.
**Before fix:**
Input: Agent says "I'll remind you tomorrow morning." (no cron.add)
Output: "I'll remind you tomorrow morning.\n\nNote: I did not schedule a reminder in this turn, so this will not trigger automatically."
## Changes
- `src/auto-reply/types.ts` — Add `systemNote?: string` field to `ReplyPayload` for internal annotations
- `src/auto-reply/reply/agent-runner.ts` — Set `systemNote` instead of modifying `text` in `appendUnscheduledReminderNote()`
- `src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts` — Update existing test + add regression test
**After fix:**
Input: Agent says "I'll remind you tomorrow morning." (no cron.add)
Output text: "I'll remind you tomorrow morning."
Output systemNote: "Note: I did not schedule a reminder in this turn, so this will not trigger automatically."
## Test plan
- [x] Updated test: guard note now asserts `systemNote` field instead of `text` concatenation
- [x] New test: "does not leak guard note into visible text" — explicitly verifies `text` does not contain the note
- [x] All 21 existing tests in `agent-runner.misc.runreplyagent.test.ts` pass
- [x] All 36 tests in `agent-runner.runreplyagent.test.ts` pass
- [x] Format check passes (`pnpm format:check`)
## Effect on User Experience
**Before:** Users see an internal system note ("Note: I did not schedule a reminder...") appended to the agent's message in Telegram and other channels.
**After:** The agent's message is clean. The guardrail note is preserved internally in `systemNote` for diagnostics but never shown to users.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Clean, focused bugfix that moves the unscheduled-reminder guardrail note from `payload.text` (visible to users in all channels) to a new `systemNote` field on `ReplyPayload` that no channel adapter reads.
- **`src/auto-reply/types.ts`**: Adds `systemNote?: string` to `ReplyPayload` with clear JSDoc annotation
- **`src/auto-reply/reply/agent-runner.ts`**: `appendUnscheduledReminderNote()` now sets `systemNote` instead of concatenating to `text`, also removing the `trimEnd()` call that is no longer needed
- **Tests**: Updated existing assertion to check `systemNote` instead of `text` concatenation, plus a new regression test explicitly verifying the note does not appear in `text`
- **`CHANGELOG.md`**: Entry added under Fixes
The fix is safe because all channel delivery paths (Telegram, Slack, Discord, Signal, WhatsApp, iMessage) only read `text`, `mediaUrl`/`mediaUrls`, and `channelData` from payloads — none read `systemNote`. The `normalizeReplyPayloadsForDelivery` function does carry `systemNote` through via spread, but downstream channel adapters destructure only the fields they need.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it's a minimal, well-tested fix that moves an internal note out of user-visible text.
- The change is small and surgical (3 lines of logic changed). All channel delivery paths were verified to only read `text`/`mediaUrl`/`mediaUrls`/`channelData` — none read `systemNote`. Tests cover both the positive case (note goes into `systemNote`) and the negative regression case (note does not appear in `text`). No new risk is introduced.
- No files require special attention.
<sub>Last reviewed commit: 03d22d1</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21271: fix(commands): pass channel/capabilities/shell/os to runtime in com...
by evansantos · 2026-02-19
77.4%
#19406: fix(heartbeat): filter error payloads from heartbeat reply selection
by namabile · 2026-02-17
76.1%
#16321: Fix #12767: suppress HEARTBEAT_OK leakage in Telegram DM replies
by tdjackey · 2026-02-14
75.1%
#21896: fix(cron): disable messaging tool when delivery.mode is none
by lailoo · 2026-02-20
74.7%
#19716: fix: inject timestamp into channel message agent context (#16442)
by chi777 · 2026-02-18
74.6%
#22982: fix: prevent stale threadId from routing subagent announces to wron...
by unboxed-ai · 2026-02-21
74.4%
#4878: fix: string/type handling and API fixes (#4537, #4380, #4373, #4547...
by lailoo · 2026-01-30
74.4%
#20294: fix(message): thread mediaLocalRoots through channel plugin dispatch
by odrobnik · 2026-02-18
74.3%
#17070: fix(telegram): Outbound: ignore empty legacy target fields
by yhw2003 · 2026-02-15
74.1%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
74.1%