Skip to content

fix(feishu): fallback when accepted turns send no visible reply#87896

Merged
steipete merged 15 commits into
openclaw:mainfrom
ArthurNie:fix/feishu-no-visible-reply-fallback
May 31, 2026
Merged

fix(feishu): fallback when accepted turns send no visible reply#87896
steipete merged 15 commits into
openclaw:mainfrom
ArthurNie:fix/feishu-no-visible-reply-fallback

Conversation

@ArthurNie

@ArthurNie ArthurNie commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add Feishu dispatcher-visible reply disposition tracking for accepted turns
  • send an explicit fallback when a dispatched Feishu turn completes without any visible output
  • suppress that fallback for intentional silent/NO_REPLY finals and for already-visible streaming/card replies
  • add targeted regression tests for empty completion, intentional silence, visible streaming close, and per-reply state reset

Why

Feishu/Lark accepted inbound turns can currently end with no user-visible output when the runtime aborts, a restart interrupts before visible content, or streaming-card terminalization fails before content is shown. In those cases the user sees silence even though the message was accepted and dispatched.

This patch makes Feishu track whether the dispatcher actually delivered visible output, rather than treating counts.final === 0 alone as proof of silence. The bot still uses the zero-final dispatch completion as the trigger point, but the dispatcher guard prevents false fallbacks after visible streaming/card output or intentional NO_REPLY.

Tests

Lightweight local checks on a constrained VPS:

  • ✅ TypeScript transpile smoke for changed files:
    • extensions/feishu/src/reply-dispatcher.ts
    • extensions/feishu/src/reply-dispatcher.test.ts
    • extensions/feishu/src/bot.ts
  • git diff --check
  • ⚠️ Targeted vitest was attempted with:
    • pnpm exec vitest run --config test/vitest/vitest.unit.config.ts extensions/feishu/src/reply-dispatcher.test.ts --runInBand
    • It did not complete on the VPS: workspace dependency hydration retried several native tarballs and the process was SIGKILLed. I did not treat that as a test pass; CI should validate the targeted test file.

Real behavior proof

  • Behavior or issue addressed: Feishu accepted inbound turns could complete without user-visible output after interruption / restart / streaming-card empty terminalization. The fix ensures an explicit visible fallback is sent when no visible reply was delivered, while preserving intentional NO_REPLY/silent behavior.

  • Real environment tested: Live OpenClaw gateway on VPS host iv-yegrn5k9vky6d72c8f3t, Feishu account default, real Feishu group chat runtime. Local runtime hotfix was applied to the installed OpenClaw/Lark runtime and the gateway was restarted.

  • Exact steps or command run after this patch: Applied the local runtime hotfix equivalent to the PR behavior, restarted the live OpenClaw gateway, then observed real Feishu runtime logs for an accepted turn reaching the empty-reply streaming fallback path instead of staying silent.

  • Evidence after fix: Redacted runtime log excerpt from the live setup:

2026-05-28T19:26:20+08:00 gateway tool: restart requested (reason=Load narrowed Feishu no-reply fallback hotfix: skip fallback for intentional silent final replies via onSkip reason tracking.)
2026-05-28T19:32:05+08:00 feishu[default][msg:om_x100b6eb62b75acacc4a0c55586a37f3]: reply completed without visible text, using empty-reply fallback

Local evidence artifacts retained on the live host:

/root/.openclaw/workspace/tmp/task-runtime/T-20260528-RUNTIME-REPLY-INTEGRITY-HOTFIX/validation-after-restart.md
/root/.openclaw/workspace/tmp/task-runtime/T-20260528-RUNTIME-REPLY-INTEGRITY-HOTFIX/wrapup-2026-05-29.md
  • Observed result after fix: The real Feishu runtime no longer left that empty terminalization path silent; it reached the visible empty-reply fallback path after the hotfix/restart. The upstream PR turns that local behavior into dispatcher-level visible-output tracking so duplicate fallback is suppressed after already-visible card/text output and intentional silent finals are not converted into user-visible warnings.

  • What was not tested: The exact upstream TypeScript patch was not package-released and installed into the live gateway yet; targeted unit coverage is included in this PR and CI is expected to validate it.

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 29, 2026
@clawsweeper

clawsweeper Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed May 31, 2026, 2:36 PM ET / 18:36 UTC.

Summary
The PR adds Feishu visible-reply tracking, a no-visible-reply fallback for accepted zero-final or failed-final turns, suppression guards for intentional/source-denied silence, and focused regression tests.

PR surface: Source +108, Tests +336. Total +444 across 9 files.

Reproducibility: no. live current-main reproduction was run in this read-only review. Source inspection plus the linked Feishu logs show the relevant current behavior: accepted turns can complete with queuedFinal=false/replies=0 and current main has no fallback path.

Review metrics: 1 noteworthy metric.

  • Dispatch result metadata: 2 fields added. Feishu fallback now depends on core dispatch metadata, so maintainers should notice the core/channel contract expansion before merge.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add redacted current-head Feishu proof for commit 95c9055; logs, terminal output, screenshots, recordings, or linked artifacts are fine if private IDs, endpoints, API keys, phone numbers, and other sensitive details are redacted.
  • Include or link the targeted current-head test output for the Feishu bot, broadcast, and reply-dispatcher tests if available.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body includes redacted live Feishu logs for an equivalent local hotfix, but not after-fix proof from the exact current PR head after the latest commits. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P2] The PR still lacks redacted real Feishu proof from the exact current head after the latest fallback-gate commits; the supplied proof is from an equivalent local hotfix.
  • [P2] This deliberately changes Feishu message delivery behavior, so a false-positive fallback would send extra visible Feishu messages even though unit coverage now exercises the intended suppression cases.

Maintainer options:

  1. Require Current-Head Feishu Proof (recommended)
    Ask for redacted logs, terminal output, or a short recording from commit 95c9055 showing both the fallback path and at least one suppression path in real Feishu.
  2. Accept Maintainer-Owned Live Proof
    A maintainer with Feishu access can verify the exact PR head themselves and record the proof in the PR before merge.

Next step before merge

  • [P1] No narrow automated code repair is indicated after the latest commits; the remaining action is current-head real Feishu proof before merge.

Security
Cleared: The diff does not change dependencies, workflows, credentials, permissions, package metadata, or other concrete security/supply-chain surfaces.

Review details

Best possible solution:

Land this after current-head Feishu proof shows the fallback appears for true no-visible accepted turns and stays suppressed for intentional silence, source-denied delivery, message-tool-only delivery, and already-visible replies.

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

No live current-main reproduction was run in this read-only review. Source inspection plus the linked Feishu logs show the relevant current behavior: accepted turns can complete with queuedFinal=false/replies=0 and current main has no fallback path.

Is this the best way to solve the issue?

Yes, this is the best bounded fix shape I found: Feishu owns the user-visible fallback while core only exposes dispatch metadata needed to avoid false fallbacks. The remaining blocker is proof of the exact current PR head, not a different code direction.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 7e0d275f7add.

Label changes

Label justifications:

  • P1: The PR addresses accepted Feishu turns that can appear ignored because no visible reply reaches the channel.
  • merge-risk: 🚨 message-delivery: The diff changes when Feishu sends or suppresses a visible fallback message after accepted dispatch completion.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body includes redacted live Feishu logs for an equivalent local hotfix, but not after-fix proof from the exact current PR head after the latest commits. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +108, Tests +336. Total +444 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 4 141 33 +108
Tests 5 337 1 +336
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 9 478 34 +444

What I checked:

  • Repository policy: Root AGENTS.md and extensions/AGENTS.md were read; the applicable guidance treats channel fallback behavior as message-delivery-sensitive and requires proof beyond diff-only review. (AGENTS.md:24, 7e0d275f7add)
  • Current main behavior: Current main has no Feishu no-visible-reply fallback symbols; the matching rg search only found existing sendPolicyDenied code outside this new fallback path. (extensions/feishu/src/bot.ts, 7e0d275f7add)
  • Fallback decision gate: The PR head gates fallback on zero-final eligible dispatches or failed queued finals, while suppressing send-policy-denied and message-tool-only source delivery. (extensions/feishu/src/bot.ts:91, 95c9055139f1)
  • Single and broadcast call sites: Both active broadcast and single-agent Feishu dispatch paths now call ensureNoVisibleReplyFallback after dispatched turns settle and failed final counts are known. (extensions/feishu/src/bot.ts:1574, 95c9055139f1)
  • Visible reply state: The Feishu dispatcher marks text, media, and non-empty streaming closes as visible and skips fallback after intentional silent final replies. (extensions/feishu/src/reply-dispatcher.ts:546, 95c9055139f1)
  • Core dispatch metadata: DispatchFromConfigResult now carries sendPolicyDenied and noVisibleReplyFallbackEligible so Feishu can distinguish true empty completion from suppressed delivery. (src/auto-reply/reply/dispatch-from-config.ts:1701, 95c9055139f1)

Likely related people:

  • steipete: Current-main blame/log in the grafted checkout points to Peter Steinberger on the Feishu reply-dispatch files, and the latest PR repair commits were authored by the same person. (role: recent area contributor and likely follow-up owner; confidence: medium; commits: 86ff92e7a88c, 304e2c83c01f, 95c9055139f1; files: extensions/feishu/src/bot.ts, extensions/feishu/src/reply-dispatcher.ts, src/auto-reply/reply/dispatch-from-config.ts)
  • ArthurNie: ArthurNie supplied the production Feishu no-visible-reply context, live hotfix proof, and the initial PR commit; this is routing context rather than current-main ownership. (role: incident reporter and source implementation author; confidence: medium; commits: 01823a97369e; files: extensions/feishu/src/bot.ts, extensions/feishu/src/reply-dispatcher.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added the rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. label May 29, 2026
@ArthurNie
ArthurNie force-pushed the fix/feishu-no-visible-reply-fallback branch from 19632ad to 80a578d Compare May 29, 2026 05:24
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 29, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels May 29, 2026
@ArthurNie
ArthurNie force-pushed the fix/feishu-no-visible-reply-fallback branch from 80a578d to f783875 Compare May 29, 2026 06:35
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 29, 2026
@ArthurNie

Copy link
Copy Markdown
Contributor Author

Additional live context from our Feishu incident triage: this PR is still highly relevant to accepted Feishu turns producing no useful visible response.

Observed adjacent symptoms in production around May 28:

I agree with the current review direction: the no-visible-reply fallback needs to cover empty streaming/card closes and active broadcast zero-final dispatches before merge. From the user-impact side, this is not cosmetic logging; it is a channel delivery integrity issue where an accepted turn can appear ignored.

@ArthurNie

Copy link
Copy Markdown
Contributor Author

Status refresh after checking the PR without pulling the repo locally:

  • Current labels still show proof: sufficient and status: ⏳ waiting on author.
  • I do not see current failing checks.
  • The blocker is not proof anymore; it is the ClawSweeper/Codex functional review finding that the patch still needs code changes before merge:
    1. Empty streaming/card closes should not mark visible output before content is actually sent.
    2. Active broadcast zero-final dispatches also need to call the no-visible-reply fallback.

So this should stay in author-action / needs-code-change state rather than being treated as ready for merge based only on green checks.

@ArthurNie

Copy link
Copy Markdown
Contributor Author

@clawsweeper automerge

Special instructions:
Fix the Feishu no-visible-reply fallback so empty streaming/card closes do not mark visible output, active broadcast dispatches call the fallback on dispatched zero-final results, and add/run targeted tests for extensions/feishu/src/reply-dispatcher.test.ts and extensions/feishu/src/bot.broadcast.test.ts.

@steipete steipete self-assigned this May 31, 2026
@steipete
steipete force-pushed the fix/feishu-no-visible-reply-fallback branch from f783875 to 8727789 Compare May 31, 2026 17:57
@openclaw-barnacle openclaw-barnacle Bot added size: L and removed size: S proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 31, 2026
@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels May 31, 2026
@steipete
steipete force-pushed the fix/feishu-no-visible-reply-fallback branch from 95c9055 to 7834e3e Compare May 31, 2026 20:27
@steipete

Copy link
Copy Markdown
Contributor

Maintainer repair/review complete for exact head 7834e3e987a3cd3bfa7793768f660c477231dc48.

What changed:

  • Kept the no-visible-reply fallback to genuinely empty/failed visible delivery, excluding send-policy-denied and message-tool-only source delivery.
  • Made Feishu fallback wait for async streaming-card idle/close side effects before checking visibility.
  • Preserved visible-delivery state across typing keepalives, cleared stale silent-skip state before later final attempts, and only treated streaming-card content as visible after CardKit accepted non-empty content.
  • Updated Feishu lifecycle mocks for the expanded dispatcher shape.

Local proof on the rebased branch:

  • node scripts/run-vitest.mjs extensions/feishu/src/monitor.lifecycle.test.ts extensions/feishu/src/bot.test.ts extensions/feishu/src/bot.broadcast.test.ts extensions/feishu/src/reply-dispatcher.test.ts extensions/feishu/src/streaming-card.test.ts — 5 files / 186 tests passed.
  • pnpm test src/auto-reply/reply/dispatch-from-config.test.ts -- -t "group silence eligible" --hookTimeout 300000 --testTimeout 300000 --reporter verbose — 2 targeted silent-policy regressions passed.
  • pnpm check:test-types — passed locally.
  • .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main — clean, no accepted/actionable findings.

CI:

  • PR run 26723589037 passed the Feishu-relevant and broad non-red checks including Real behavior proof, Scan changed paths, Security High gates that ran, Critical Quality channel/agent gates, check-prod-types, check-dependencies, check-guards, check-shrinkwrap, and the node auto-reply shards.
  • The remaining PR reds are current-main reds with the same failing job names on main run 26723623047: check-lint, check-test-types, check-additional-boundaries-bcd, and check-additional-extension-bundled. Their failures point at unrelated Telegram/WhatsApp/channel-ingress surfaces, not this PR's touched Feishu/auto-reply files.

Known gap: no new live Feishu account run was performed; this was covered with focused dispatcher/bot/lifecycle/streaming-card regression tests plus CI Real behavior proof.

@steipete
steipete merged commit 7c15c27 into openclaw:main May 31, 2026
154 of 158 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 1, 2026
…claw#87896)

* fix(feishu): fallback when accepted turns send no visible reply

* fix(feishu): cover no-visible-reply fallback gaps

* fix(feishu): mark media replies visible

* fix(feishu): honor suppressed delivery fallback

* test(auto-reply): trim fallback test churn

* fix(feishu): gate empty fallback eligibility

* test(auto-reply): expect fallback metadata after denied dispatch

* fix(feishu): fallback after failed visible final sends

* test(feishu): keep reply dispatcher mock shape aligned

* fix(auto-reply): respect silent policy for no-visible fallback

* fix(feishu): wait for streaming close before fallback

* fix(feishu): clear silent skip before later finals

* fix(feishu): preserve visible state across keepalives

* test(feishu): align lifecycle dispatcher mocks

* fix(feishu): require accepted streaming content for fallback

---------

Co-authored-by: ArthurNie <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
@ArthurNie

Copy link
Copy Markdown
Contributor Author

CI triage for current head 7834e3e987a3cd3bfa7793768f660c477231dc48 after maintainer repair:

This does not look like missing user-side proof anymore; it is CI/code-quality cleanup on the repaired head.

Current failing checks I inspected:

  • check-lint
    • oxlint reports 9 core errors + 6 extension errors, including:
      • empty fallbacks in spreads are unnecessary
      • type parameter used only once
      • no-op type conversions
      • import should be type-only to avoid runtime side-effect import
      • unexpected await of a non-Promise value
  • check-test-types
    • src/channels/message/ingress-queue.test.ts(54,29): Property 'payload' does not exist on type 'ChannelIngressQueueCompletedRecord<...> | ChannelIngressQueueFailedRecord | ChannelIngressQueueRecord<...> | ChannelIngressQueueClaim<...>'
  • check-additional-boundaries-bcd
    • lint:plugins:no-extension-test-core-imports flags extension tests importing core internals, e.g. Telegram/WhatsApp tests importing src/channels/message/ingress-queue.js, state DB, kysely-sync.
  • check-additional-extension-bundled
    • same bundled-extension oxlint class as above.

So the actionable next step appears to be a maintainer/ClawSweeper cleanup pass on lint/type/boundary issues for the repaired head, not more production proof from Arthur.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper could not start a re-review for this item.

Reason: re-review requires an open issue or PR.

SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…claw#87896)

* fix(feishu): fallback when accepted turns send no visible reply

* fix(feishu): cover no-visible-reply fallback gaps

* fix(feishu): mark media replies visible

* fix(feishu): honor suppressed delivery fallback

* test(auto-reply): trim fallback test churn

* fix(feishu): gate empty fallback eligibility

* test(auto-reply): expect fallback metadata after denied dispatch

* fix(feishu): fallback after failed visible final sends

* test(feishu): keep reply dispatcher mock shape aligned

* fix(auto-reply): respect silent policy for no-visible fallback

* fix(feishu): wait for streaming close before fallback

* fix(feishu): clear silent skip before later finals

* fix(feishu): preserve visible state across keepalives

* test(feishu): align lifecycle dispatcher mocks

* fix(feishu): require accepted streaming content for fallback

---------

Co-authored-by: ArthurNie <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…claw#87896)

* fix(feishu): fallback when accepted turns send no visible reply

* fix(feishu): cover no-visible-reply fallback gaps

* fix(feishu): mark media replies visible

* fix(feishu): honor suppressed delivery fallback

* test(auto-reply): trim fallback test churn

* fix(feishu): gate empty fallback eligibility

* test(auto-reply): expect fallback metadata after denied dispatch

* fix(feishu): fallback after failed visible final sends

* test(feishu): keep reply dispatcher mock shape aligned

* fix(auto-reply): respect silent policy for no-visible fallback

* fix(feishu): wait for streaming close before fallback

* fix(feishu): clear silent skip before later finals

* fix(feishu): preserve visible state across keepalives

* test(feishu): align lifecycle dispatcher mocks

* fix(feishu): require accepted streaming content for fallback

---------

Co-authored-by: ArthurNie <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants