Skip to content

fix(feishu): add 30 s request timeout to streaming-card API calls#102948

Merged
sallyom merged 3 commits into
openclaw:mainfrom
hugenshen:fix/feishu-streaming-card-timeouts
Jul 11, 2026
Merged

fix(feishu): add 30 s request timeout to streaming-card API calls#102948
sallyom merged 3 commits into
openclaw:mainfrom
hugenshen:fix/feishu-streaming-card-timeouts

Conversation

@hugenshen

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where all six outbound CardKit and tenant-token requests in
extensions/feishu/src/streaming-card.ts called fetchWithSsrFGuard without
a timeoutMs deadline. If the Feishu API endpoint accepts a TCP connection but
never completes the HTTP response (stalled server, network middlebox, proxy delay),
the affected call blocks indefinitely and pins a gateway request lane open.

The critical path is getToken(): tenant-token refresh is called before every
CardKit request, so a single stalled token endpoint blocks the entire streaming-card
reply pipeline for a running agent turn.

Why This Change Was Made

fetchWithSsrFGuard exposes a top-level timeoutMs parameter that applies a hard
abort deadline to the underlying request. Adding FEISHU_STREAMING_CARD_REQUEST_TIMEOUT_MS = 30_000
and wiring it to all six call sites in one constant covers every streaming-card path
without touching callers.

The 30-second value matches the sibling pattern already used across the codebase
(e.g. extensions/googlechat/src/api.ts, extensions/google-meet/src/meet.ts,
extensions/mattermost/src/api.ts).

extensions/feishu/src/app-registration.ts was checked and is not affected: its
fetchFeishuJson helper receives signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
through the caller's init, providing equivalent protection via a different mechanism.

User Impact

Before: A stalled Feishu API endpoint leaves tenant-token refresh or any CardKit
request pending indefinitely. No timeout error is surfaced; the streaming-card reply
lane is blocked until the process is restarted.

After: Every streaming-card API request fails with an abort/timeout error after
30 seconds. The caller receives a concrete error, the lane is released, and the gateway
can continue processing other messages.

Evidence

Branch: fix/feishu-streaming-card-timeouts
Base: upstream/main
Node: v22.22.0 — macOS Darwin 24.3.0 arm64

Existing tests — all passing after change

$ node scripts/run-vitest.mjs extensions/feishu/src/streaming-card.test.ts --reporter=verbose

 ✓ FeishuStreamingSession > rejects oversized streaming tenant-token JSON before buffering the full body
 ✓ FeishuStreamingSession > rejects oversized streaming card-create JSON before buffering the full body
 ✓ FeishuStreamingSession > flushes throttled pending text after the throttle window
 ✓ FeishuStreamingSession > handles a rejected scheduled flush update
 ✓ FeishuStreamingSession > pushes natural-boundary updates immediately inside the throttle window
 ✓ FeishuStreamingSession > retries cumulative content after a failed streaming update
 ✓ FeishuStreamingSession > retries cumulative content after a non-OK streaming update
 ✓ FeishuStreamingSession > replaces content when final text removes transient streamed status
 ✓ FeishuStreamingSession > drops a surrogate pair whole when truncating the closeout summary
 ✓ FeishuStreamingSession > logs a final replacement failure when CardKit returns non-OK
 ✓ FeishuStreamingSession > reports no visible content when final close update fails before any accepted text
 ✓ FeishuStreamingSession > bounds streaming token cache lifetime when token expiry overflows
 ✓ FeishuStreamingSession > bounds streaming token fallback lifetime when the process clock is invalid
 ✓ FeishuStreamingSession > treats an invalid process clock as a streaming token cache miss
 ... (7 more)

 Test Files  1 passed (1)
      Tests  21 passed (21)
   Duration  10.74s

Format and lint

$ git diff --check   →  (no output, clean)
$ node scripts/run-oxlint.mjs extensions/feishu/src/streaming-card.ts
→ exit 0, no warnings

Diff summary

extensions/feishu/src/streaming-card.ts | 11 +++++ (constant + 6 × timeoutMs)

Call sites covered:

  • feishu.streaming-card.token (tenant token refresh)
  • feishu.streaming-card.create (card entity creation)
  • feishu.streaming-card.update (content stream update)
  • feishu.streaming-card.replace (content replace)
  • feishu.streaming-card.note-update (note footer update)
  • feishu.streaming-card.close (streaming mode close)

What was not tested: a live Feishu API stall scenario. Crabbox/Testbox remote CI
was not run at submission time.

  • AI-assisted (Cursor / Claude Sonnet 4.6)
  • I understand what the code does
  • Change is focused and does not mix unrelated concerns

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: XS labels Jul 9, 2026
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 11, 2026, 2:30 PM ET / 18:30 UTC.

Summary
Passes the existing account-resolved Feishu HTTP timeout into streaming-card sessions, applies it to six guarded requests, and adds propagation plus stalled-request regression coverage.

PR surface: Source +18, Tests +67. Total +85 across 4 files.

Reproducibility: yes. at source level. Current main leaves six guarded streaming-card requests without explicit deadlines, and the contributor's stalled-endpoint output plus the exact-head regression test exercise the abort path.

Review metrics: 1 noteworthy metric.

  • Existing timeout policy propagation: 1 existing policy reaches 6 requests. The patch closes the complete streaming-card deadline gap without adding a config key, environment variable, or default.

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 automated repair is needed; wait for routine exact-head checks, then use ordinary maintainer merge handling for the reviewed head.

Security
Cleared: The patch stays inside the existing SSRF-guarded request boundary and adds no dependency, workflow, permission, secret, artifact-download, publishing, or package-resolution surface.

Review details

Best possible solution:

Land the exact-head design after routine checks: resolve the established Feishu timeout at the account boundary, carry that prepared value into the streaming session, and enforce it consistently on every tenant-token and CardKit guarded request.

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

Yes at source level. Current main leaves six guarded streaming-card requests without explicit deadlines, and the contributor's stalled-endpoint output plus the exact-head regression test exercise the abort path.

Is this the best way to solve the issue?

Yes. Reusing the established account-resolved Feishu timeout and carrying it into the session is the narrowest maintainable fix; it avoids both a second hardcoded policy and a risky global guarded-fetch default change.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 5a5424474dcd.

Label changes

Label justifications:

  • P2: A stalled Feishu endpoint can indefinitely block one channel's streaming-card reply path, but the blast radius is limited and the fix is focused.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): Observed terminal output shows a Feishu-compatible stalled tenant-token request aborting after the new deadline, and the exact head retains deterministic configured-timeout regression coverage.
  • proof: sufficient: Contributor real behavior proof is sufficient. Observed terminal output shows a Feishu-compatible stalled tenant-token request aborting after the new deadline, and the exact head retains deterministic configured-timeout regression coverage.
Evidence reviewed

PR surface:

Source +18, Tests +67. Total +85 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 20 2 +18
Tests 2 68 1 +67
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 88 3 +85

What I checked:

  • Current-main behavior: Current main calls fetchWithSsrFGuard for tenant-token refresh and five CardKit operations without timeoutMs, so a connected endpoint that never completes can leave the streaming-card path pending. (extensions/feishu/src/streaming-card.ts:132, 5a5424474dcd)
  • Existing Feishu timeout contract: Feishu already resolves a direct field, environment override, account config, or the 30-second default through one bounded helper; the PR reuses this policy instead of creating configuration or a competing default. (extensions/feishu/src/client-timeout.ts:15, 5a5424474dcd)
  • Guarded-fetch contract: fetchWithSsrFGuard turns timeoutMs into an abort signal for the complete guarded operation and also passes it to the underlying dispatcher. (src/infra/net/fetch-guard.ts:460, 5a5424474dcd)
  • Exact-head implementation: The PR resolves the configured timeout once from the runtime account, carries it in session credentials, and applies it to all six streaming-card guarded requests with the existing default as a constructor fallback. (extensions/feishu/src/reply-dispatcher.ts:384, 189ed2819d09)
  • Regression coverage: The exact head verifies the configured value reaches the streaming session and deterministically aborts a stalled tenant-token fetch after a short configured deadline. (extensions/feishu/src/streaming-card.test.ts:322, 189ed2819d09)
  • Real behavior proof: The contributor posted observed output from a Feishu-compatible endpoint that accepted the tenant-token request and stalled; the request aborted after approximately 30 seconds with a timeout error. (d4a902c5073a)

Likely related people:

  • sallyom: Authored the follow-up aligning the PR with the existing Feishu timeout contract, repaired the final test mock, and documented the selected narrow solution. (role: recent area contributor and likely follow-up owner; confidence: high; commits: 59832b41df15, 189ed2819d09; files: extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/streaming-card.ts, extensions/feishu/src/reply-dispatcher.test.ts)
  • m1heng: The earlier review explicitly routed the Feishu timeout surface to this account, providing an adjacent ownership signal where local per-line history is ambiguous. (role: adjacent Feishu review contact; confidence: medium; files: extensions/feishu/src/streaming-card.ts, extensions/feishu/src/client-timeout.ts)
  • Peter Steinberger: Current-main blame and file history route the relevant Feishu files through a recent broad repository integration commit, which is useful as an integration signal but weak as feature-specific ownership. (role: recent repository integrator; confidence: low; commits: 03cab2950525; files: extensions/feishu/src/streaming-card.ts, extensions/feishu/src/reply-dispatcher.ts, extensions/feishu/src/client-timeout.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.
Review history (5 earlier review cycles)
  • reviewed 2026-07-09T15:37:05.249Z sha e49ba11 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-11T08:56:01.846Z sha d4a902c :: needs real behavior proof before merge. :: [P2] Remove the boundary-breaking proof script
  • reviewed 2026-07-11T09:06:50.595Z sha d4a902c :: needs changes before merge. :: [P2] Remove the boundary-breaking proof script
  • reviewed 2026-07-11T14:38:49.925Z sha b1dcdaa :: needs maintainer review before merge. :: none
  • reviewed 2026-07-11T18:16:24.717Z sha 59832b4 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added 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. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
@hugenshen

Copy link
Copy Markdown
Contributor Author

Stall-timeout behavior proof

Added commit d4a902c5073 with a hermetic local Feishu-compatible HTTP server that accepts the tenant-token request but never completes the response, plus a regression test.

Real 30s abort (local proof script)

```bash
$ node --import tsx extensions/feishu/scripts/streaming-card-stall-proof.ts
[stall-proof] starting FeishuStreamingSession.start(); expecting abort in ~30000ms
[stall-proof] received stalled Feishu tenant-token request: POST /open-apis/auth/v3/tenant_access_token/internal
[fetch-timeout] fetch timeout after 30000ms (elapsed 30005ms) operation=fetchWithSsrFGuard url=http://127.0.0.1:49255/open-apis/auth/v3/tenant_access_token/internal
[stall-proof] aborted after 30094ms with: request timed out
[stall-proof] PASS stalled Feishu-compatible request aborted after 30094ms
```

Regression test

```bash
$ node scripts/run-vitest.mjs extensions/feishu/src/streaming-card.test.ts --reporter=verbose
Test Files 1 passed (1)
Tests 22 passed (22)
```

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 11, 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 Jul 11, 2026
@hugenshen
hugenshen force-pushed the fix/feishu-streaming-card-timeouts branch from d4a902c to b1dcdaa Compare July 11, 2026 14:25
@clawsweeper clawsweeper Bot added 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. labels Jul 11, 2026
Signed-off-by: sallyom <somalley@redhat.com>
@sallyom

sallyom commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Maintainer follow-up pushed in 59832b41df1.

This preserves the PR's six plugin-local request deadlines, but uses resolveConfiguredHttpTimeoutMs(account) instead of adding a second hardcoded 30-second policy. The default remains 30 seconds, while existing config, per-account, and environment overrides are now honored consistently. Tests cover timeout propagation and the stalled-request case.

This is best narrow fix: it keeps the request-boundary approach while reusing the shipped Feishu timeout contract.

Signed-off-by: sallyom <somalley@redhat.com>
@sallyom

sallyom commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Merge-ready at HEAD 189ed2819d09.

This fixes a real Feishu streaming-card stall by adding request-local deadlines. Reusing the existing account-resolved Feishu timeout is the best narrow fix: it preserves the 30-second default while honoring established config, per-account, and environment overrides. No breaking changes or compatibility concerns found.

Exact-head CI is green with no failing or pending checks, the worktree is clean, and both maintainer autoreview and ClawSweeper are clean on this solution.

@sallyom
sallyom merged commit 473df17 into openclaw:main Jul 11, 2026
92 checks passed
steipete pushed a commit that referenced this pull request Jul 11, 2026
…02948)

* fix(feishu): bound streaming-card API requests with a 30s timeout

* fix(feishu): honor configured streaming timeout

Signed-off-by: sallyom <somalley@redhat.com>

* test(feishu): declare streaming mock credentials

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: NIO <nocodet@mail.com>
Co-authored-by: sallyom <somalley@redhat.com>
(cherry picked from commit 473df17)
(cherry picked from commit 591df2e)
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 12, 2026
…enclaw#102948)

* fix(feishu): bound streaming-card API requests with a 30s timeout

* fix(feishu): honor configured streaming timeout

Signed-off-by: sallyom <somalley@redhat.com>

* test(feishu): declare streaming mock credentials

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: NIO <nocodet@mail.com>
Co-authored-by: sallyom <somalley@redhat.com>
chovizzz added a commit to chovizzz/openclaw that referenced this pull request Jul 16, 2026
chovizzz added a commit to chovizzz/openclaw that referenced this pull request Jul 16, 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 P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants