Skip to content

fix(discord): skip history backfill when channel lacks .history#26301

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/discord-backfill-history-guard
Closed

fix(discord): skip history backfill when channel lacks .history#26301
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/discord-backfill-history-guard

Conversation

@briandevans

@briandevans briandevans commented May 15, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a defensive hasattr(channel, "history") guard at the start of _fetch_channel_context in gateway/platforms/discord.py. Restores 4 e2e tests in tests/e2e/test_discord_adapter.py that have been failing on every open PR since commit 4abfb6bc2 (feat(discord): default history backfill on, expand to per-user + threads). Adds a focused regression test in tests/gateway/test_discord_free_response.py.

Root cause: the _handle_message path added by 4abfb6bc2 unconditionally calls _fetch_channel_context for non-DM channels, which jumps straight into async for msg in channel.history(...). Channel-like objects that don't implement history — Forum parent channels, voice channels in some deployments, and the SimpleNamespace channel proxies used by tests/e2e/test_discord_adapter.py — raise AttributeError. The outer except discord.Forbidden: at gateway/platforms/discord.py:3730 cannot catch it cleanly: under mocked-discord test setups discord is a MagicMock, so Python evaluates discord.Forbidden as the except class before matching, which raises TypeError: catching classes that do not inherit from BaseException is not allowed. That TypeError then masks the original AttributeError and bubbles past the broader except Exception handler.

Related Issue

Follow-up to commit 4abfb6bc2. No standalone bug issue — surfaced as 4 e2e regressions on every open PR.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • gateway/platforms/discord.py — early return "" from _fetch_channel_context when the channel doesn't expose history. Same end state as a real fetch that yielded zero usable messages.
  • tests/gateway/test_discord_free_response.py — new test_fetch_channel_context_returns_empty_when_channel_lacks_history covering a bare SimpleNamespace channel, asserting empty-string return and no exception.

How to Test

  1. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/gateway/test_discord_free_response.py tests/e2e/test_discord_adapter.py -v
  2. Expected: 41/41 passing (35 free-response + 6 e2e).
  3. Regression guard: temporarily revert the production line; the new test fails with the exact TypeError: catching classes that do not inherit from BaseException is not allowed from CI logs. Restore the fix — all 6 _fetch_channel_context unit tests pass.

The 4 previously-failing e2e tests now restored:

  • TestMentionStrippedCommandDispatch::test_mention_then_command
  • TestMentionStrippedCommandDispatch::test_nickname_mention_then_command
  • TestMentionStrippedCommandDispatch::test_text_before_command_not_detected
  • TestAutoThreadingPreservesCommand::test_command_detected_after_auto_thread

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass (41/41)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — defensive guard is platform-independent
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Related / Positioning

  • Follow-up to commit 4abfb6bc2 (feat(discord): default history backfill on, expand to per-user + threads).
  • No other open PR touches _fetch_channel_context or channel.history (verified via gh search prs).

Audited siblings: gateway/platforms/discord.py has no other call sites of channel.history(...) — the guard is appropriately localised. No widening needed.

Copilot AI review requested due to automatic review settings May 15, 2026 11:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR prevents Discord channel context backfill from raising when a “channel-like” object doesn’t implement history, and adds a regression test to lock in the behavior.

Changes:

  • Add an early return in _fetch_channel_context when channel.history is missing.
  • Add an async regression test covering channel objects without history.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/gateway/test_discord_free_response.py Adds a regression test ensuring missing history returns empty context instead of raising.
gateway/platforms/discord.py Guards _fetch_channel_context against channel objects that lack history.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3662 to +3667
# channels, voice channels, and custom proxies in tests can lack it.
# Skip rather than raise: the outer ``except`` swallows ``discord.Forbidden``
# only when ``discord`` is the real library, so an AttributeError that
# leaks past would surface as a TypeError under mocked-discord test
# setups (the except clause can't evaluate a MagicMock as an exception).
if not hasattr(channel, "history"):
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/discord Discord bot adapter labels May 15, 2026
NishantEC

This comment was marked as outdated.

@briandevans

Copy link
Copy Markdown
Contributor Author

CI audit — all 3 Tests failures are pre-existing baselines on clean origin/main (db84a78). Zero failures are in touched code.

Test Symptom Root cause on main
tests/run_agent/test_provider_parity.py::TestDeveloperRoleSwap::test_developer_role_via_nous_portal ValueError: Model has a context window of 15,000 tokens Test instantiates Nous Portal with empty model=; fallback resolves to a 15,000-token context which trips the 64K minimum guard at run_agent.py:2349.
TestBuildApiKwargsNousPortal::test_includes_nous_product_tags (same) (same)
TestBuildApiKwargsNousPortal::test_uses_chat_completions_format (same) (same)

Reproduces locally on clean main; already being addressed by #26048.

The new `_fetch_channel_context` path added by 4abfb6b calls
`channel.history(...)` unconditionally. Channel-like objects that do not
implement history — Forum parent channels, voice channels in some
deployments, and custom proxies in tests — raise AttributeError. The
outer `except discord.Forbidden:` cannot catch it: under mocked-discord
test setups (e.g. tests/e2e/test_discord_adapter.py), `discord` is a
MagicMock and evaluating `discord.Forbidden` as an exception class raises
`TypeError: catching classes that do not inherit from BaseException is
not allowed`, which masks the original error and bubbles up.

Symptom: 4 e2e tests in `tests/e2e/test_discord_adapter.py` started
failing on every PR after 4abfb6b landed:

    test_mention_then_command
    test_nickname_mention_then_command
    test_text_before_command_not_detected
    test_command_detected_after_auto_thread

The fix is a defensive guard: if the channel does not expose `history`,
silently return an empty backfill string. This is the same end state as
any successful backfill that yields zero usable messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans briandevans force-pushed the fix/discord-backfill-history-guard branch from 07edd4a to 934a3c0 Compare May 30, 2026 05:11
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to focus the queue on security/file-safety work where civilian merges are landing. Happy to reopen if maintainers want this picked up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/discord Discord bot adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants