Skip to content

Fix/send poll intent detection clean#65301

Closed
weichengdeng wants to merge 2 commits into
openclaw:mainfrom
weichengdeng:fix/send-poll-intent-detection-clean
Closed

Fix/send poll intent detection clean#65301
weichengdeng wants to merge 2 commits into
openclaw:mainfrom
weichengdeng:fix/send-poll-intent-detection-clean

Conversation

@weichengdeng

Copy link
Copy Markdown
Contributor

Summary

Describe the problem and fix in 2–5 bullets:

If this PR fixes a plugin beta-release blocker, title it fix(<plugin-id>): beta blocker - <summary> and link the matching Beta blocker: <plugin-name> - <summary> issue labeled beta-blocker. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.

  • Problem: message-action-runner treated any poll-related params on action="send" as poll intent, including metadata-only fields like duration and boolean flags.
  • Why it matters: normal sends could be rejected even when the payload did not actually contain a poll question or options.
  • What changed: send now only treats non-empty pollQuestion or pollOption as real poll intent, and regression tests were added for both the relaxed send path and the retained real-poll rejection path.
  • What did NOT change (scope boundary): poll creation behavior, poll validation rules, and poll-only parameter parsing for actual action="poll" requests were not changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause: the send-side guard reused broad poll-parameter detection that was designed to notice any poll-related fields, not to distinguish real poll payloads from metadata-only params.
  • Missing detection / guardrail: there was no narrower intent check requiring actual poll content such as a question or options before rejecting action="send".
  • Contributing context (if known): tool schemas and channel-specific params can populate poll metadata fields independently of a real poll payload, which made the broad check too aggressive.

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/poll-params.test.ts, src/infra/outbound/message-action-runner.core-send.test.ts
  • Scenario the test should lock in: action="send" must succeed when only poll metadata is present, and must still reject when pollQuestion or pollOption indicates a real poll payload.
  • Why this is the smallest reliable guardrail: the regression lives in local parameter classification and send routing, so focused unit tests cover the failure mode without requiring channel end-to-end setup.
  • Existing test that already covers this (if any): there was partial coverage for zero-valued poll params, but not for non-question metadata-only poll params on send.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

message send no longer rejects non-poll sends just because poll metadata fields are present. Real poll payloads on action="send" are still rejected and must use action="poll".

Diagram (if applicable)

For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write N/A.

Before:
[send action + poll metadata only] -> [broad poll param check] -> [rejected as poll]

After:
[send action + poll metadata only] -> [real poll intent check] -> [sent normally]
[send action + pollQuestion/pollOption] -> [real poll intent check] -> [rejected, use poll action]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: local repo checkout
  • Model/provider: N/A
  • Integration/channel (if any): outbound message action routing
  • Relevant config (redacted): minimal test config with outbound test plugin

Steps

  1. Run runMessageAction with action: "send" and metadata-only poll fields such as pollDurationHours, pollMulti, or pollAnonymous.
  2. Observe behavior before the fix: the request is rejected with Poll fields require action "poll".
  3. Run the same scenario after the fix and confirm the send succeeds.
  4. Run runMessageAction with action: "send" plus pollQuestion or pollOption.
  5. Confirm the request is still rejected and must use action: "poll".

Expected

  • Metadata-only poll params do not force action="send" into the poll-only rejection path.
  • Real poll payloads still require action="poll".

Actual

  • Before the fix, metadata-only poll params could incorrectly trigger poll rejection.
  • After the fix, only real poll content triggers that rejection.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: targeted unit tests for hasRealPollIntent and runMessageAction send routing were run locally.
  • Edge cases checked: empty question, empty option array, metadata-only params, and real poll question payloads.
  • What you did not verify: live channel delivery against real Telegram/Discord/other integrations was not manually exercised.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: some caller may have implicitly depended on metadata-only poll params being rejected on action="send".
    • Mitigation: the new tests lock the intended behavior to actual poll content only, which matches the error message and action contract more closely.

@greptile-apps

greptile-apps Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a false-positive poll intent rejection on action="send" by replacing the broad hasPollCreationParams check with a new, stricter hasRealPollIntent function that only considers a non-empty pollQuestion or pollOption as real poll intent — metadata-only fields like pollDurationHours, pollMulti, pollAnonymous, and pollPublic no longer cause sends to be incorrectly rejected. The fix is well-scoped, the new hasPollCreationParams function is preserved for actual poll creation paths, and the regression tests cover the key boundary conditions clearly.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped, correct, and backed by comprehensive regression tests.

No P0 or P1 findings. The narrowing of the send-side poll intent guard from hasPollCreationParams to hasRealPollIntent is logically sound: readSnakeCaseParamRaw correctly handles both camelCase and snake_case variants, all edge cases (empty arrays, whitespace strings, non-string entries, null/undefined) are handled defensively, and the original broader function is preserved for actual poll creation paths downstream. Test coverage is thorough at both unit and integration levels.

No files require special attention.

Reviews (1): Last reviewed commit: "test: align send validation with real po..." | Re-trigger Greptile

@clawsweeper

clawsweeper Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and the useful part of this older PR is already implemented there.

Close: current main now has the same-or-better poll metadata send behavior through a merged replacement PR that preserved source credit, so this conflicting source branch no longer has unique mergeable work.

Root-cause cluster
Relationship: superseded
Canonical: #93441
Summary: This source PR and the merged replacement address the same outbound message.send false positive; the replacement is now the canonical merged implementation.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

So I’m closing this older PR as already covered on main rather than keeping a mostly-duplicated branch open.

Review details

Best possible solution:

Keep #93441 as the canonical merged fix and close this conflicting source branch while preserving its credit trail.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: the older release path rejects action="send" when channel-extra poll-prefixed metadata is treated as poll intent, and the PR discussion includes a matching production Discord payload. I did not execute tests because this review was read-only.

Is this the best way to solve the issue?

Yes for cleanup: the merged replacement folds the behavior into the existing hasPollCreationParams contract and covers the remaining channel-extra metadata case without keeping a parallel source branch open.

Security review:

Security review cleared: Security review cleared: the source diff only changes TypeScript poll-intent classification and tests, with no dependency, secret, workflow, package, network, or execution-surface change.

AGENTS.md: found and applied where relevant.

What I checked:

  • Current main source behavior: hasPollCreationParams now treats only content-bearing pollQuestion or pollOption values as poll intent; shared modifiers and channel-specific metadata are documented as schema-padded send noise. (src/poll-params.ts:30, 7d4001c85547)
  • Current main send guard still protects real poll content: runMessageAction still rejects action="send" when hasPollCreationParams(params) is true, so the current-main change narrows intent detection without removing the poll-action contract. (src/infra/outbound/message-action-runner.ts:1411, 7d4001c85547)
  • Regression coverage on current main: Current tests allow plain sends with schema-padded pollDurationSeconds, pollPublic, pollAnonymous, and pollOptionIndex while retaining rejection for content-bearing poll fields. (src/infra/outbound/message-action-runner.send-validation.test.ts:384, 7d4001c85547)
  • Merged replacement provenance: The merged replacement PR states it repairs this source PR, credits @weichengdeng, and landed as merge commit 073343e2e29422bd30192b50eaf2348738f7e861. (073343e2e294)
  • Fix commit timestamp: The fixing merge commit is 073343e2e29422bd30192b50eaf2348738f7e861, committed at 2026-06-16T09:29:16+08:00, with subject fix(outbound): ignore schema-padded poll metadata on send. (073343e2e294)
  • Release provenance: No tag contains the fixing commit, and latest release v2026.6.8 still has the older channel-specific poll-prefixed detection loop, so the fix is current-main-only rather than shipped in the latest release. (src/poll-params.ts:108, 844f405ac1be)

Likely related people:

  • weichengdeng: Authored this source PR and is credited as co-author on the merged replacement commit that carried the useful behavior forward. (role: source contributor credited in merged replacement; confidence: high; commits: 45fb8fab48bb, ea95a6334134, 4d9329c473d9; files: src/poll-params.ts, src/infra/outbound/message-action-runner.ts, src/infra/outbound/message-action-runner.send-validation.test.ts)
  • codezz: Authored the merged earlier fix that narrowed shared poll modifier detection and shaped the same send-vs-poll boundary. (role: recent area contributor; confidence: high; commits: 0fd95756cd4a, 8b546facaf49; files: src/poll-params.ts, src/poll-params.test.ts, src/infra/outbound/message-action-runner.send-validation.test.ts)
  • vincentkoc: Authored the replacement branch merge-update commits and appears in recent current-main blame/history around the merged poll metadata implementation. (role: recent replacement branch contributor; confidence: medium; commits: a4baf7dcb670, 97a4a13e0da0, 3b7729779a3d; files: src/poll-params.ts, src/infra/outbound/message-action-runner.send-validation.test.ts, src/infra/outbound/message-action-runner.core-send.test.ts)
  • gumadeiras: Earlier poll gating and Telegram poll schema commits shaped the shared-versus-channel-extra poll parameter boundary used by this fix. (role: prior poll contract contributor; confidence: medium; commits: 6dfd39c32f79, d8b95d2315eb; files: src/poll-params.ts, src/poll-params.test.ts, extensions/telegram/src/message-tool-schema.ts)
  • Bartok9: Authored the earlier zero-valued poll-param mitigation in the same helper and tests, part of the same schema-default noise class. (role: adjacent behavior contributor; confidence: medium; commits: c70ae1c96e7e; files: src/poll-params.ts, src/poll-params.test.ts)

Codex review notes: model internal, reasoning high; reviewed against 7d4001c85547; fix evidence: commit 073343e2e294, main fix timestamp 2026-06-16T09:29:16+08:00.

@clawsweeper clawsweeper Bot added the mantis: telegram-visible-proof Mantis should capture Telegram visible proof. label May 12, 2026
@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 12, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 19, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@fanyangCS

Copy link
Copy Markdown

AI-generated review comment.
I tested this PR locally on the PR branch and the targeted checks pass:

  • Installed dependencies with corepack pnpm install --frozen-lockfile
  • Ran targeted Vitest coverage for the touched behavior:
    • src/poll-params.test.ts
    • src/infra/outbound/message-action-runner.send-validation.test.ts
    • src/infra/outbound/message-action-runner.core-send.test.ts
  • Ran oxlint on touched files: 0 warnings / 0 errors
  • Ran oxfmt --check on touched files: passed

I also ran a focused source-level probe mirroring the failure mode we observed with GPT/default-filled tool params:

  • { pollQuestion: "", pollOption: [], pollDurationHours: 1, pollMulti: false }
    • hasPollCreationParams(...) === true
    • hasRealPollIntent(...) === false
  • { pollDurationHours: 24, pollMulti: false }
    • hasPollCreationParams(...) === true
    • hasRealPollIntent(...) === false
  • { pollQuestion: "Lunch?", pollDurationHours: 1 }
    • hasRealPollIntent(...) === true
  • { pollOption: ["Yes", "No"], pollDurationHours: 1 }
    • hasRealPollIntent(...) === true

This matches the intended behavior: metadata/default-only poll fields no longer block action: "send", while real poll content still requires action: "poll".

Verdict: I think this is safe to merge. The change is small, targeted, and directly addresses the false-positive poll guard without changing actual action: "poll" creation/validation behavior.

Non-blocking suggestions:

  1. Clean up the PR description. It still contains template text such as “Describe the problem and fix in 2–5 bullets”.
  2. Consider adding an explicit regression test for the GPT-5.4/5.5 default-filled params shape, e.g. pollQuestion: "", pollOption: [], pollDurationHours: 1 or 24. The existing tests cover the behavior substantively, but an exact-shape regression would make the original failure mode obvious.
  3. The PR intentionally allows action: "send" with poll metadata-only fields, even odd ones like negative durations. That seems like the right tradeoff because metadata-only poll fields should be ignored on the send path, but a short code comment may help future maintainers understand why send does not validate those metadata fields.

@fanyangCS

Copy link
Copy Markdown

Real behavior proof from a Discord production deployment that matches this PR's fix shape.

Environment:

  • OpenClaw: 2026.6.1 (2e08f0f)
  • @openclaw/discord: 2026.6.1
  • Channel: Discord guild channel
  • Model/runtime: github-copilot/gpt-5.5 via embedded agent

A normal text reply was generated, but message.send failed before delivery because schema/default poll metadata was interpreted as poll intent:

{
  "action": "send",
  "channel": "discord",
  "target": "channel:<redacted>",
  "message": "<plain text>",
  "pollQuestion": "",
  "pollOption": [],
  "pollDurationHours": 24,
  "pollMulti": false,
  "pollOptionIndex": 1,
  "pollDurationSeconds": 60,
  "pollAnonymous": false,
  "pollPublic": false
}

Observed error:

Poll fields require action "poll"; use action "poll" instead of "send".

I compared this exact payload against the current installed hasPollCreationParams logic. With the already-merged #89601-style behavior, empty pollQuestion/pollOption and shared modifiers (pollDurationHours, pollMulti) no longer trigger the guard. But pollDurationSeconds: 60 is still a channel-extra poll field, so it still returns true and blocks action="send".

This PR's hasRealPollIntent approach should fix the exact failure: the above payload has no non-empty pollQuestion and no non-empty pollOption, so it should not block a plain send; real poll content (pollQuestion: "Lunch?" or pollOption: ["A", "B"]) would still be rejected under action="send" and routed to action="poll" as intended.

Impact: message loss in Discord channel sessions. The user sees the bot react/acknowledge, but no answer is posted.

@openclaw-clownfish

Copy link
Copy Markdown
Contributor

Clownfish 🐠 reef update

Thanks for the contribution. Clownfish hit a branch-permission wall on this PR, so it opened a replacement branch to keep review moving while preserving credit.

Replacement PR: #93441
Source PR: #65301
This source PR remains open; the replacement is just the writable fix lane.
The replacement PR carries the original credit trail forward.

fish notes: model gpt-5.5, reasoning medium; reviewed against 4d9329c.

@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

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

Labels

mantis: telegram-visible-proof Mantis should capture Telegram visible proof. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

2 participants