Skip to content

fix(auto-reply): guard missing dispatcher getFailedCounts without weakening the SDK type#89148

Closed
Alix-007 wants to merge 1 commit into
openclaw:mainfrom
Alix-007:fix/89116-dispatch-failed-counts
Closed

fix(auto-reply): guard missing dispatcher getFailedCounts without weakening the SDK type#89148
Alix-007 wants to merge 1 commit into
openclaw:mainfrom
Alix-007:fix/89116-dispatch-failed-counts

Conversation

@Alix-007

@Alix-007 Alix-007 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #89116

Summary

getDispatcherFinalOutcomeCounts() in src/auto-reply/reply/dispatch-from-config.ts called dispatcher.getFailedCounts().final without optional chaining, while the adjacent getCancelledCounts?.().final ?? 0 line already guarded against the method being absent. Some ReplyDispatcher variants omit count methods at runtime (e.g. plugin-provided dispatchers), so the unguarded call threw TypeError: dispatcher.getFailedCounts is not a function.

Per the P1 review feedback, this revision preserves the public ReplyDispatcher.getFailedCounts contract as required (no plugin-facing SDK break) and tolerates missing readers internally instead:

  • reply-dispatcher.types.tsgetFailedCounts stays required in the SDK-visible ReplyDispatcher (diff is purely additive vs main); adds an internal DispatcherOutcomeCountsView (optional readers) + a readDispatcherFailedCounts() helper for tolerant reads
  • dispatch-from-config.tsgetDispatcherFinalOutcomeCounts takes the narrower DispatcherOutcomeCountsView; the active-dispatch wrapper falls back to empty counts via the helper
  • dispatch-acp-delivery.ts — visible-text delivery reads failed counts through the helper
  • bot.ts — Feishu no-visible-reply fallback reads failed counts defensively

The crash is fixed through tolerant internal reads without changing the exported dispatcher type.

Real behavior proof

Behavior addressed: A ReplyDispatcher variant that omits getFailedCounts at runtime no longer crashes the final-outcome accounting path; the failed count safely resolves to 0, while dispatchers that implement it keep using the real counts. The SDK-visible type is unchanged from main.

Real environment tested: Linux x86_64, Node.js v22.22.0, HEAD 9a1d408b520a44cfe00abb0538ca039c2460035a rebased onto origin/main 489efc8f5eb15066f007f820ea71398357ad08e8. The production function getDispatcherFinalOutcomeCounts is driven directly via node --import tsx, plus the focused Vitest regression.

Exact steps or command run after this patch:

node --import tsx -e 'import("./src/auto-reply/reply/dispatch-from-config.ts").then((m) => {
  const partial = { getCancelledCounts: () => ({ tool: 0, block: 0, final: 2 }) };
  let beforeErr = "(no error)";
  try { partial.getFailedCounts().final; } catch (e) { beforeErr = e.constructor.name + ": " + e.message; }
  console.log("BEFORE:", beforeErr);
  console.log("AFTER :", JSON.stringify(m.getDispatcherFinalOutcomeCounts(partial)));
  const full = { getCancelledCounts: () => ({ tool:0, block:0, final:1 }), getFailedCounts: () => ({ tool:0, block:0, final:5 }) };
  console.log("FULL  :", JSON.stringify(m.getDispatcherFinalOutcomeCounts(full)));
})'

OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs run \
  src/auto-reply/reply/dispatch-from-config.final-outcome-counts.test.ts \
  extensions/feishu/src/bot.test.ts --reporter=dot

Evidence after fix:

BEFORE: TypeError: partial.getFailedCounts is not a function
AFTER : {"cancelled":2,"failed":0}
FULL  : {"cancelled":1,"failed":5}

      Tests  73 passed (73)

Observed result after fix: The unguarded pre-fix call reproduces the reported TypeError. After the patch, the production getDispatcherFinalOutcomeCounts returns {cancelled: 2, failed: 0} for the method-less dispatcher (no throw) and {cancelled: 1, failed: 5} for a full dispatcher. The focused regression + Feishu bot suites pass 73/73, and CI check-test-types / check-prod-types / check-lint are green on this head. The public ReplyDispatcher.getFailedCounts type is identical to main.

What was not tested: End-to-end live dispatch against a real gateway run; the fix is exercised through the production accounting function with representative dispatcher variants and unit tests rather than a full live reply cycle.

@openclaw-barnacle openclaw-barnacle Bot added size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 1, 2026
@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: passed. Reviewed June 1, 2026, 11:47 PM ET / 03:47 UTC.

Summary
The PR adds tolerant internal dispatcher failed-count reads in auto-reply accounting and Feishu fallback paths while preserving the required ReplyDispatcher.getFailedCounts contract.

PR surface: Source +24, Tests +35. Total +59 across 5 files.

Reproducibility: yes. Source inspection shows current main calls dispatcher.getFailedCounts().final without a guard, so a runtime dispatcher missing that method follows a clear TypeError path; I did not execute tests in this read-only review.

Review metrics: 1 noteworthy metric.

  • Dispatcher contract: 0 public fields changed; 1 internal counts view added. The measured shape matters because making ReplyDispatcher.getFailedCounts optional would be a plugin SDK compatibility change, while this patch keeps that public contract intact.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

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

Next step before merge

  • No ClawSweeper repair lane is needed; review findings are empty and the existing PR can proceed through automerge or maintainer landing gates.

Security
Cleared: The diff only changes internal TypeScript dispatcher-count reads and focused tests; it does not add dependency, CI, secret, install, or code-execution surface.

Review details

Best possible solution:

Land the narrow internal guard with the focused regression coverage while keeping the plugin-facing dispatcher API unchanged.

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

Yes. Source inspection shows current main calls dispatcher.getFailedCounts().final without a guard, so a runtime dispatcher missing that method follows a clear TypeError path; I did not execute tests in this read-only review.

Is this the best way to solve the issue?

Yes. The PR fixes the crash at the internal count-reading boundary and covers the sibling direct call sites without changing the SDK-visible ReplyDispatcher type.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (terminal): The PR body includes after-fix terminal proof against the production helper plus focused Vitest/Feishu test output for the changed behavior.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: 🚀 automerge armed.

Label justifications:

  • P1: The PR fixes a reported dispatch crash in a channel/auto-reply workflow that can affect real users on the latest release.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (terminal): The PR body includes after-fix terminal proof against the production helper plus focused Vitest/Feishu test output for the changed behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal proof against the production helper plus focused Vitest/Feishu test output for the changed behavior.
Evidence reviewed

PR surface:

Source +24, Tests +35. Total +59 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 4 31 7 +24
Tests 1 35 0 +35
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 66 7 +59

What I checked:

  • Repository policy read: Root and extensions AGENTS.md were read; the relevant guidance is to review beyond the diff and treat plugin SDK compatibility as upgrade-sensitive. (AGENTS.md:1, 7c4fb1bd2cde)
  • Current-main crash point: Current main calls dispatcher.getFailedCounts().final inside final-outcome accounting while the adjacent cancelled count is optional-chained, so a dispatcher without that method can throw before the PR. (src/auto-reply/reply/dispatch-from-config.ts:783, 7c4fb1bd2cde)
  • Sibling optional pattern: The public dispatch finalizer already reads failed counts with optional chaining, which supports the PR's internal tolerant-read shape. (src/auto-reply/dispatch.ts:419, 7c4fb1bd2cde)
  • SDK contract surface: ReplyDispatcher is exported through openclaw/plugin-sdk/reply-runtime, so preserving the required getFailedCounts field avoids a plugin-facing API change. (src/plugin-sdk/reply-runtime.ts:48, 7c4fb1bd2cde)
  • PR diff keeps the SDK type and adds an internal view: The PR leaves ReplyDispatcher.getFailedCounts required, adds DispatcherOutcomeCountsView plus readDispatcherFailedCounts, and switches the affected internal reads to the helper or optional chaining. (src/auto-reply/reply/reply-dispatcher.types.ts:20, 9a1d408b520a)
  • Feishu call sites covered: Current main has two Feishu no-visible-reply fallback reads that call dispatcher.getFailedCounts() directly; the PR changes both to tolerate a missing method. (extensions/feishu/src/bot.ts:1578, 7c4fb1bd2cde)

Likely related people:

  • Omar Shahine: Current blame and git log -S show commit 02192bd introduced the visible unguarded dispatcher-count paths in the sampled checkout. (role: introduced current behavior; confidence: high; commits: 02192bd27fd6; files: src/auto-reply/reply/dispatch-from-config.ts, src/auto-reply/reply/dispatch-acp-delivery.ts, src/auto-reply/reply/reply-dispatcher.types.ts)
  • lobster: The same commit message records Reviewed-by: @lobster, making this a useful routing signal for the affected dispatcher/typing change. (role: reviewer; confidence: medium; commits: 02192bd27fd6; files: src/auto-reply/reply/dispatch-from-config.ts, extensions/feishu/src/bot.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 proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels Jun 1, 2026
@Alix-007 Alix-007 force-pushed the fix/89116-dispatch-failed-counts branch from 0839372 to 2ab1a0d Compare June 1, 2026 16:36
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 1, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 1, 2026
@Alix-007 Alix-007 force-pushed the fix/89116-dispatch-failed-counts branch from 2ab1a0d to 38abdc7 Compare June 1, 2026 23:12
@Alix-007

Alix-007 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Addressed the rank-up move: the two Feishu dispatcher.getFailedCounts() call sites (extensions/feishu/src/bot.ts) now use the same ?.() ?? { tool: 0, block: 0, final: 0 } fallback as the core dispatch paths, so every in-repo consumer is guarded and extension type validation passes.

On the [P1] SDK-visibility note: making getFailedCounts optional mirrors the already-optional getCancelledCounts on the same ReplyDispatcher type and reflects the real runtime contract that triggered #89116 (some dispatcher variants omit the method). All in-repo callers are now guarded. Whether external plugin consumers warrant treating this as a compat break is a maintainer call — happy to switch to keeping the type required and guarding only the single accounting site instead, if you'd prefer that shape.

@clawsweeper

clawsweeper Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: S and removed size: XS labels Jun 1, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 1, 2026
@Alix-007 Alix-007 force-pushed the fix/89116-dispatch-failed-counts branch from 38abdc7 to 8993d63 Compare June 2, 2026 00:01
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 2, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 2, 2026
@clawsweeper clawsweeper Bot added status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 2, 2026
@Alix-007

Alix-007 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main and refreshed the real-behavior proof for the current head 8993d632a5. The getFailedCounts crash is reproduced with the pre-fix logic and shown resolved by driving the real exported getDispatcherFinalOutcomeCounts (no throw → {cancelled:2,failed:0}), alongside the focused Vitest regression (3/3 passed). check-test-types / check-prod-types / check-lint are green on this head.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 2, 2026
…kening the SDK type

getDispatcherFinalOutcomeCounts called dispatcher.getFailedCounts()
without optional chaining while the adjacent getCancelledCounts?.() was
guarded, so dispatcher variants omitting getFailedCounts threw
"TypeError: dispatcher.getFailedCounts is not a function".

Keep the public ReplyDispatcher.getFailedCounts contract required (no
plugin-facing SDK break) and add an internal DispatcherOutcomeCountsView
plus a readDispatcherFailedCounts helper for the defensive accounting
paths in core dispatch (dispatch-from-config, dispatch-acp-delivery) and
the Feishu extension bot, each falling back to zero counts when a
non-conforming runtime dispatcher omits the method.

Fixes openclaw#89116

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Alix-007 Alix-007 force-pushed the fix/89116-dispatch-failed-counts branch from 8993d63 to 9a1d408 Compare June 2, 2026 01:38
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 2, 2026
@Alix-007 Alix-007 changed the title fix(auto-reply): guard optional getFailedCounts on dispatcher variants fix(auto-reply): guard missing dispatcher getFailedCounts without weakening the SDK type Jun 2, 2026
@Alix-007

Alix-007 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Reworked per the P1 review feedback. Instead of making the SDK-visible ReplyDispatcher.getFailedCounts optional, this now keeps that contract required (no plugin-facing break) and tolerates missing readers internally via a new DispatcherOutcomeCountsView + readDispatcherFailedCounts helper. The reply-dispatcher.types.ts diff is now purely additive — the public type is identical to main.

Re-verified at head 9a1d408b52: the crash repro + fix proof (getDispatcherFinalOutcomeCounts returns {cancelled:2,failed:0} for a method-less dispatcher, no throw) and the focused regression + Feishu bot suites (73/73) all pass; check-test-types/check-prod-types/check-lint green.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 2, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper saw the passing review, but the PR needs another repair pass before merge.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=9a1d408b520a44cfe00abb0538ca039c2460035a); failed required checks before automerge: Real behavior proof:CANCELLED
Action: repair worker queued. Run: https://github.com/openclaw/clawsweeper/actions/runs/26797132936
Model: gpt-5.5

I will update this PR branch, or open a safe credited replacement, if the repair worker finds a narrow CI fix.

Automerge progress:

  • 2026-06-02 03:42:32 UTC review queued 9a1d408b520a (queued)
  • 2026-06-02 03:47:52 UTC review passed 9a1d408b520a (structured ClawSweeper verdict: pass (sha=9a1d408b520a44cfe00abb0538ca039c24600...)

@clawsweeper clawsweeper Bot added clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 2, 2026
@clawsweeper

clawsweeper Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the contribution here. ClawSweeper tried the original lane first, but branch permissions blocked the push, so a replacement PR is carrying the fix forward.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #89318
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this one because the run was configured to close superseded source PRs after opening the replacement.
Credit follows the fix over to the replacement PR. no sneaky treasure grab.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 0bdfb4a.

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

Labels

channel: feishu Channel integration: feishu clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(dispatch): getFailedCounts is not a function - missing optional chaining

2 participants