Repository navigation
fix(bridges): silence (file not found:) Discord/Telegram noise from prose-quoted markers - #987
Conversation
## The bug
An agent's reply body sometimes contains an inline-code description
of the file-marker convention itself, e.g.:
Body example like `[file: /tmp/sutando-x.png]` reached the user…
The bridge's `[file:|send:|attach:]` regex is markdown-context-blind:
it extracts every matching substring regardless of whether it sits
inside backticks, a fenced code block, a blockquote, or plain prose.
For prose-quoted markers, the path doesn't exist, and the
`(file not found: ...)` Discord/Telegram message lands in the user's
DM as confusing noise AFTER the agent's clean reply.
## Fix: minimum user-impact
Two layers were considered:
1. Make extraction markdown-aware. Principled but invasive (regex ->
real parser; reaches every send path).
2. Silence the user-facing warning for the missing-file case. Real
typos still surface to operators via stderr; only the user-
visible Discord/Telegram noise goes away.
This PR ships sonichi#2. Replaces four `(file not found:)` send sites with
stderr log lines:
- src/discord-bridge.py: poll_results, poll_proactive,
poll_dm_fallback channel-redirect
- src/telegram-bridge.py: send_reply
Each carries a comment pointing back to this bug class so the next
refactor doesn't restore the warning by accident.
## What's NOT silenced
`(file not allowed: ...)` / `(file access denied: ...)` — the
allowlist rejection branch. That's a security signal (attempted
exfil of a file outside SEND_ALLOWED_ROOTS / SEND_ALLOWED_PREFIXES)
worth surfacing to both user and operator. Test pins this contract.
## Tests
`tests/discord-bridge-prose-marker.test.py` (3 cases) — source-grep
regression guards:
- no `.send(f"(file not found: ...)` patterns remain in discord-bridge
- telegram-bridge specifically pinned alongside Discord (bridges
historically drift on shared patterns)
- `(file not allowed: ...)` still surfaces (security signal)
Source-grep is the right shape — invoking the async poll_* methods
end-to-end would need a discord.py harness, and the fix is a local
behavioral change in well-bounded branches that grep verifies
precisely.
Co-Authored-By: Vasiliy's Sutando <noreply@anthropic.com>
|
LGTM. Right call to pick the minimum-impact layer — making the marker-extraction markdown-aware would have been a bigger refactor with its own edge-case surface (nested backticks, indented blocks, escaped backticks), and the missing-file case is genuinely "agent wrote prose about a file-marker convention", not actionable user information. Critically, the security signal ( Replacing the four warning-send sites with stderr lines preserves operator visibility for real typos (operator sees stderr) without polluting the user-visible reply (user doesn't). Right asymmetry. The "comment at each replacement pointing back to this bug class" pattern is the kind of thing that prevents a future refactor from silently restoring the warning. Worth keeping intact. |
…rs (sonichi#987) ## The bug An agent's reply body sometimes contains an inline-code description of the file-marker convention itself, e.g.: Body example like `[file: /tmp/sutando-x.png]` reached the user… The bridge's `[file:|send:|attach:]` regex is markdown-context-blind: it extracts every matching substring regardless of whether it sits inside backticks, a fenced code block, a blockquote, or plain prose. For prose-quoted markers, the path doesn't exist, and the `(file not found: ...)` Discord/Telegram message lands in the user's DM as confusing noise AFTER the agent's clean reply. ## Fix: minimum user-impact Two layers were considered: 1. Make extraction markdown-aware. Principled but invasive (regex -> real parser; reaches every send path). 2. Silence the user-facing warning for the missing-file case. Real typos still surface to operators via stderr; only the user- visible Discord/Telegram noise goes away. This PR ships sonichi#2. Replaces four `(file not found:)` send sites with stderr log lines: - src/discord-bridge.py: poll_results, poll_proactive, poll_dm_fallback channel-redirect - src/telegram-bridge.py: send_reply Each carries a comment pointing back to this bug class so the next refactor doesn't restore the warning by accident. ## What's NOT silenced `(file not allowed: ...)` / `(file access denied: ...)` — the allowlist rejection branch. That's a security signal (attempted exfil of a file outside SEND_ALLOWED_ROOTS / SEND_ALLOWED_PREFIXES) worth surfacing to both user and operator. Test pins this contract. ## Tests `tests/discord-bridge-prose-marker.test.py` (3 cases) — source-grep regression guards: - no `.send(f"(file not found: ...)` patterns remain in discord-bridge - telegram-bridge specifically pinned alongside Discord (bridges historically drift on shared patterns) - `(file not allowed: ...)` still surfaces (security signal) Source-grep is the right shape — invoking the async poll_* methods end-to-end would need a discord.py harness, and the fix is a local behavioral change in well-bounded branches that grep verifies precisely. Co-authored-by: Vasiliy's Sutando <noreply@anthropic.com> Co-authored-by: Chi Wang <4250911+sonichi@users.noreply.github.com>
The bug
An agent's reply body sometimes contains inline-code prose describing the file-marker convention itself, e.g.:
The bridge's marker regex is markdown-context-blind, extracts the substring, finds no such file at
/tmp/sutando-x.png, and ships(file not found: /tmp/sutando-x.png)as a Discord/Telegram message after the agent's clean reply. Confusing user-visible noise.Fix: minimum user-impact
Two layers considered:
This PR ships #2. Replaces four
(file not found:)send sites with stderr log lines:src/discord-bridge.pypoll_results(channel replies)src/discord-bridge.pypoll_proactive(owner DMs)src/discord-bridge.pypoll_dm_fallbackchannel-redirect branchsrc/telegram-bridge.pysend_replyEach replacement carries a comment pointing back to this bug class so the next refactor doesn't restore the warning by accident.
What's NOT silenced
(file not allowed: ...)/(file access denied: ...)— the allowlist rejection branch. That's a security signal worth surfacing to both user and operator. Test pins this contract.Tests
tests/discord-bridge-prose-marker.test.py(3 cases) — source-grep regression guards. Invoking the asyncpoll_*methods end-to-end would need a discord.py harness, and the fix is a local behavioral change in well-bounded branches that grep verifies precisely.Test plan
python3 tests/discord-bridge-prose-marker.test.py— 3/3 pass_is_path_sendable, chunker, or marker extraction itself)[file: /no-such-path]substring inside backticks — verify the bridge logs[file marker, file not found — likely a prose quotation]to stderr and ships NO follow-up message to Discord🤖 Generated with Claude Code