Skip to content

Harden Discord reads and message targeting#61572

Closed
jimjammcjames wants to merge 1 commit intoopenclaw:mainfrom
moves-social:moo/discord-read-hardening
Closed

Harden Discord reads and message targeting#61572
jimjammcjames wants to merge 1 commit intoopenclaw:mainfrom
moves-social:moo/discord-read-hardening

Conversation

@jimjammcjames
Copy link
Copy Markdown

Summary\n- require explicit provider/target separation for message actions\n- remove legacy target aliases and current-thread fallback for read\n- default Discord reads to compact 20-message payloads with diagnostics\n\n## Testing\n- export PATH="/opt/homebrew/opt/node@22/bin:/Users/jamesdugle/.nvm/versions/node/v18.20.2/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Users/jamesdugle/.codex/tmp/arg0/codex-arg0iL3mXJ:/Users/jamesdugle/.bun/bin:/opt/homebrew/share/google-cloud-sdk/bin:/Users/jamesdugle/.local/bin:/Users/jamesdugle/.antigravity/antigravity/bin:/opt/homebrew/opt/openjdk@11/bin:/Users/jamesdugle/.nvm/versions/node/v18.20.2/bin:/Applications/Codex.app/Contents/Resources"\n- corepack pnpm exec vitest run src/infra/outbound/channel-selection.test.ts src/infra/outbound/message-action-normalization.test.ts src/infra/outbound/message-action-runner.test.ts src/agents/tools/discord-actions.test.ts\n- export PATH="/opt/homebrew/opt/node@22/bin:/Users/jamesdugle/.nvm/versions/node/v18.20.2/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/pkg/env/active/bin:/opt/pmk/env/global/bin:/Library/Apple/usr/bin:/Users/jamesdugle/.codex/tmp/arg0/codex-arg0iL3mXJ:/Users/jamesdugle/.bun/bin:/opt/homebrew/share/google-cloud-sdk/bin:/Users/jamesdugle/.local/bin:/Users/jamesdugle/.antigravity/antigravity/bin:/opt/homebrew/opt/openjdk@11/bin:/Users/jamesdugle/.nvm/versions/node/v18.20.2/bin:/Applications/Codex.app/Contents/Resources" && corepack pnpm build

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: L labels Apr 6, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e1654bb10

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +276 to +279
detail: Type.Optional(
stringEnum(["compact", "raw"], {
description:
"Read payload detail level. compact is the safe default for Discord history; raw keeps the full normalized payload.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward detail when adapting Discord read actions

The new detail parameter is now advertised in the message tool schema, but the Discord read adapter still forwards only limit, before, after, and around to handleDiscordAction (see src/channels/plugins/actions/discord/handle-action.ts:150-161). In practice this makes detail: "raw" a no-op for message-tool callers, who will still get compact payloads even though the schema says raw is supported. Please pass detail through (or remove it from the schema) so behavior matches the contract.

Useful? React with 👍 / 👎.

Comment on lines 20 to 23
if (
!explicitTarget &&
!hasLegacyTarget &&
action !== "read" &&
actionRequiresTarget(action) &&
!actionHasTarget(action, normalizedArgs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve internal edit calls that still pass to

This normalization change removed the legacy to/channelId canonicalization path before validation, so downstream applyTargetToParams now throws whenever to is present for target-taking actions. An internal caller still uses that shape: tryEditToolMessage invokes runMessageAction with action: "edit" and to in src/auto-reply/reply/dispatch-acp-delivery.ts:94-104. That means ACP tool-update edits now fail and fall back to posting extra messages instead of updating in place.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 6, 2026

Greptile Summary

This PR hardens Discord reads and message targeting by defaulting readMessages to a compact 20-message payload with diagnostics logging, removing the auto-inferred current-thread fallback for the read action, and rejecting legacy to/channelId params in favour of an explicit target field for all target-taking actions. It also adds a requireExplicitTarget gate in createMessageTool for send-family actions and improves channel-selection error messages when a caller accidentally passes a target ID in the channel field.

The logic is sound and the new test coverage is thorough. Two issues worth addressing:

  • Misleading test names (P1): Three tests in message-action-runner.test.ts are named with "blocks ... when ..." language but their assertions confirm the operation succeeds (result.kind === \"send\" or \"action\"). Future developers reading these names will assume the cross-context policy already rejects these cases, which could lead to skipping real guards when tightening the policy.
  • Redundant channelId read (P2): In the readMessages case of discord-actions-messaging.ts, resolveChannelId() and requestedTarget both call readStringParam(params, \"channelId\", { required: true }). Extracting the raw read before resolution makes the pre/post-resolution distinction explicit at a glance.

Confidence Score: 4/5

Safe to merge; the P1 concern is in test descriptions only with no runtime regression risk.

Core logic changes are well-structured and covered by tests. The misleading test names create long-term maintainability debt around the cross-context messaging policy but do not affect runtime behaviour.

src/infra/outbound/message-action-runner.test.ts (misleading test names at lines 239, 253, 310); src/agents/tools/discord-actions-messaging.ts (redundant read at lines 340-343).

Comments Outside Diff (1)

  1. src/infra/outbound/message-action-runner.test.ts, line 239-251 (link)

    P1 Misleading test names assert success, not rejection

    Three tests use "blocks" in their name but assert the operation succeeds:

    • Line 239: "blocks send when target differs from current channel" asserts expect(result.kind).toBe("send")
    • Line 253: "blocks thread-reply when channelId differs from current channel" asserts expect(result.kind).toBe("action")
    • Line 310: "blocks $name send when target differs from current context" asserts expect(result.kind).toBe("send")

    The word "blocks" implies the operation should be rejected, but the assertions confirm it succeeds. A future developer reading these names will assume cross-context guards already exist when they do not. Suggested renames: "allows same-provider send when target differs from current channel", "allows same-provider thread-reply when target differs from current channel", and "allows $name send when target differs from current context".

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/infra/outbound/message-action-runner.test.ts
    Line: 239-251
    
    Comment:
    **Misleading test names assert success, not rejection**
    
    Three tests use "blocks" in their name but assert the operation succeeds:
    
    - Line 239: `"blocks send when target differs from current channel"` asserts `expect(result.kind).toBe("send")`
    - Line 253: `"blocks thread-reply when channelId differs from current channel"` asserts `expect(result.kind).toBe("action")`
    - Line 310: `"blocks $name send when target differs from current context"` asserts `expect(result.kind).toBe("send")`
    
    The word "blocks" implies the operation should be rejected, but the assertions confirm it succeeds. A future developer reading these names will assume cross-context guards already exist when they do not. Suggested renames: `"allows same-provider send when target differs from current channel"`, `"allows same-provider thread-reply when target differs from current channel"`, and `"allows $name send when target differs from current context"`.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/outbound/message-action-runner.test.ts
Line: 239-251

Comment:
**Misleading test names assert success, not rejection**

Three tests use "blocks" in their name but assert the operation succeeds:

- Line 239: `"blocks send when target differs from current channel"` asserts `expect(result.kind).toBe("send")`
- Line 253: `"blocks thread-reply when channelId differs from current channel"` asserts `expect(result.kind).toBe("action")`
- Line 310: `"blocks $name send when target differs from current context"` asserts `expect(result.kind).toBe("send")`

The word "blocks" implies the operation should be rejected, but the assertions confirm it succeeds. A future developer reading these names will assume cross-context guards already exist when they do not. Suggested renames: `"allows same-provider send when target differs from current channel"`, `"allows same-provider thread-reply when target differs from current channel"`, and `"allows $name send when target differs from current context"`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/tools/discord-actions-messaging.ts
Line: 340-343

Comment:
**`channelId` read twice in `readMessages` case**

`resolveChannelId()` on line 340 internally calls `readStringParam(params, "channelId", { required: true })`. Lines 341-343 call the same function again to populate `requestedTarget`. Consider extracting the raw read before resolution to make the pre/post distinction explicit:

```suggestion
      const requestedTarget = readStringParam(params, "channelId", {
        required: true,
      });
      const channelId = resolveDiscordChannelId(requestedTarget);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Harden Discord message reads" | Re-trigger Greptile

Comment on lines 340 to +343
const channelId = resolveChannelId();
const requestedTarget = readStringParam(params, "channelId", {
required: true,
});
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.

P2 channelId read twice in readMessages case

resolveChannelId() on line 340 internally calls readStringParam(params, "channelId", { required: true }). Lines 341-343 call the same function again to populate requestedTarget. Consider extracting the raw read before resolution to make the pre/post distinction explicit:

Suggested change
const channelId = resolveChannelId();
const requestedTarget = readStringParam(params, "channelId", {
required: true,
});
const requestedTarget = readStringParam(params, "channelId", {
required: true,
});
const channelId = resolveDiscordChannelId(requestedTarget);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/discord-actions-messaging.ts
Line: 340-343

Comment:
**`channelId` read twice in `readMessages` case**

`resolveChannelId()` on line 340 internally calls `readStringParam(params, "channelId", { required: true })`. Lines 341-343 call the same function again to populate `requestedTarget`. Consider extracting the raw read before resolution to make the pre/post distinction explicit:

```suggestion
      const requestedTarget = readStringParam(params, "channelId", {
        required: true,
      });
      const channelId = resolveDiscordChannelId(requestedTarget);
```

How can I resolve this? If you propose a fix, please make it concise.

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!

@jimjammcjames jimjammcjames force-pushed the moo/discord-read-hardening branch from 3e1654b to a4e187e Compare April 6, 2026 02:20
@openclaw-barnacle openclaw-barnacle Bot added the channel: discord Channel integration: discord label Apr 6, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4e187ede0

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

} from "../send.js";
import type { DiscordSendComponents, DiscordSendEmbeds } from "../send.shared.js";
import { resolveDiscordChannelId } from "../targets.js";
import { logDebug, logWarn } from "../../../../src/logger.js";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove core-src import from Discord action runtime

extensions/discord/src/actions/runtime.messaging.ts now imports ../../../../src/logger.js, which violates the extension boundary documented in extensions/AGENTS.md/AGENTS.md (extension production code must not import core src/**). This can break Discord action loading in standalone/bundled plugin environments where the extension package is resolved without the repository root source tree, causing runtime module resolution failures. Please route logging through a plugin-local seam or a public SDK/runtime surface instead of a direct core-path import.

Useful? React with 👍 / 👎.

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 28, 2026

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

This PR is superseded by the narrower merged fix in #73521. Current main now addresses the Discord read/search hang and inbound hook session-key fallback without taking this PR's broader target-schema changes, and this PR's diff also carried extension-boundary and compatibility risks called out in review.

Best possible solution:

Close this PR as superseded by the merged #73521 fix. Keep the targeted main implementation for Discord read/search timeout, gateway routing, and session-key fallback; handle any future message-target API tightening separately with compatibility and extension-boundary review.

What I checked:

  • Superseding commit on main: Current main contains merged commit e4ff7c1, fix: Discord read/search timeout, session-key fallback, and gateway execution mode (#73521), which is the maintainer-authored follow-up that explicitly superseded this approach in the provided related PR context. (e4ff7c162044)
  • Discord REST timeout now lives in the request client: Current main defines a 15 second Discord REST timeout and injects AbortSignal.timeout(DISCORD_REST_TIMEOUT_MS) into the Discord request-client fetch wrapper, covering read/search REST calls at the HTTP layer. (extensions/discord/src/proxy-request-client.ts:6, e4ff7c162044)
  • Read/search now route through gateway execution mode: Discord channel actions now return gateway execution mode for read and search, matching the defense-in-depth part of the superseding fix. (extensions/discord/src/channel-actions.ts:162, e4ff7c162044)
  • Inbound hook session-key fallback is implemented: dispatchReplyFromConfig now falls back from SessionKey to CommandTargetSessionKey, so internal message:received hook emission is not skipped when Discord's primary session key is empty. (src/auto-reply/reply/dispatch-from-config.ts:295, e4ff7c162044)
  • Regression coverage exists for the superseding fix: Current tests assert the Discord request client injects an abort timeout signal, including a hanging-request case. (extensions/discord/src/proxy-request-client.test.ts:5, e4ff7c162044)
  • Current main deliberately keeps legacy target compatibility: The main branch still canonicalizes legacy to/channelId fields into target; that means this PR's broader breaking target-schema change was not adopted by the superseding fix. (src/infra/outbound/message-action-normalization.ts:48, c0ec58f4b690)

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Codex review notes: model gpt-5.5, reasoning high; reviewed against c0ec58f4b690; fix evidence: commit e4ff7c162044.

@clawsweeper clawsweeper Bot closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: discord Channel integration: discord size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant