Skip to content

feishu: replace deprecated card V2 action container with column_set#79832

Open
xiabee wants to merge 2 commits into
openclaw:mainfrom
xiabee:feishu-card-v2-action-to-column-set
Open

feishu: replace deprecated card V2 action container with column_set#79832
xiabee wants to merge 2 commits into
openclaw:mainfrom
xiabee:feishu-card-v2-action-to-column-set

Conversation

@xiabee

@xiabee xiabee commented May 9, 2026

Copy link
Copy Markdown

Summary

Fixes #79824.

Feishu's V2 card schema dropped the V1 { tag: "action", actions: [...] }
container. The Open Platform API now rejects every button-bearing card with:

http_status: 400
feishu_code: 230099
feishu_msg : Failed to create card content, ext=ErrCode: 200861;
             ErrPath : ROOT -> body -> elements -> [1](tag: action);
             ErrMsg  : cards of schema V2 no longer support this capability;
             ErrorValue: unsupported tag action

End-user impact: no Feishu approval card, /quick-actions card, or buttons
block reaches the chat at all
— the agent silently hangs waiting on
approvals the operator never sees, and gateway/delivery-recovery keeps
re-firing the failing send.

This is distinct from the previously-fixed 230099 reports (#53310, #53771)
which were about ErrCode 200621 / parse card json err in markdown cards.
The new sub-code is 200861 / unsupported tag action, surfaced after Feishu
tightened V2 schema validation.

Change

Add a small actionRowToColumnSet(buttons) helper in outbound.ts that
emits the V2-supported shape — a column_set with one auto-width column per
button — and route the four production sites through it:

File Site
extensions/feishu/src/outbound.ts sanitizeNativeFeishuCardElement
extensions/feishu/src/outbound.ts buildFeishuCardElementForBlock("buttons")
extensions/feishu/src/card-ux-approval.ts exec/plugin approval card
extensions/feishu/src/card-ux-launcher.ts /quick actions card

sanitizeNativeFeishuCardElement:

  • Still accepts the legacy V1 input shape { tag: "action", actions: [...] }
    for back-compat with callers that hand-build native cards.
  • Now also accepts the V2 input shape { tag: "column_set", columns: [...] },
    flattening single-button columns through the existing button sanitizer.
  • Always emits the V2 shape.

The emitted column_set uses flex_mode: "none", horizontal_spacing: "default",
and one column per button with width: "auto" + vertical_align: "center",
giving the same horizontal-row layout V1 action produced.

Real behavior proof

Behavior addressed: Feishu Open Platform rejects every exec-approval and
/quick-actions card built by @openclaw/feishu because the cards include a
top-level { tag: "action", actions: [...] } element that V2 schema dropped.
The card never reaches Feishu, so approval flows silently hang and
gateway/delivery-recovery floods retries.

Real environment tested: OpenClaw 2026.5.7 (eeef486) gateway running
on Kylin Linux V10 SP1 (Node v24.15.0), @openclaw/[email protected] installed
via openclaw plugins install, Feishu Lark Win desktop client (May 2026
build) connected to a real Feishu tenant. Patch was applied as a hot-rewrite
on the production dist/send-DowxxbpH.js (recursive walk wrapping every
JSON.stringify(card) to convert {tag:"action", actions:[...]} into
{tag:"column_set", columns:[...]}) — semantically identical to the source
edits in this PR. Hot-patch was syntax-checked with node --check before
restarting the gateway via systemctl --user restart openclaw-gateway.

Exact steps or command run after this patch:

  1. cp dist/send-DowxxbpH.js dist/send-DowxxbpH.js.bak
  2. Inject __ocMigrateAction(card) recursive rewriter and replace both
    JSON.stringify(card) call sites in dist/send-DowxxbpH.js.
  3. node --check dist/send-DowxxbpH.jssyntax OK.
  4. rm /tmp/jiti/dist-send-*.cjs to invalidate any jiti compile cache.
  5. systemctl --user daemon-reload && systemctl --user restart openclaw-gateway.
  6. From the Feishu DM thread, run a sandbox-elevated request that triggers
    gateway/exec-approvals (mainSecurity: fullAsk, Mode: foreground).
  7. Click one of the buttons rendered on the resulting approval card.

After-fix evidence:

Before the fix, every approval delivery failed with the V2 schema rejection
(redacted runtime log excerpt copied from /tmp/openclaw/openclaw-2026-05-09.log):

2026-05-09T09:36:09.573Z error gateway/exec-approvals exec approvals: failed to deliver to feishu:user:ou_910c***: Error: Feishu card send failed: {"code":"ERR_BAD_REQUEST","method":"post","url":"https://open.feishu.cn/open-apis/im/v1/messages","http_status":400,"feishu_code":230099,"feishu_msg":"Failed to create card content, ext=ErrCode: 200861; ErrPath: ROOT -> body -> elements -> [1](tag: action); ErrMsg: cards of schema V2 no longer support this capability; ErrorValue: unsupported tag action; ","feishu_log_id":"20260509173608A84A9EA31C488390F6D6"}
2026-05-09T09:40:08.430Z warn  gateway/delivery-recovery Retry failed for delivery 98bd38d1-***: Feishu card send failed: ... feishu_code 230099 ...

After applying the rewrite + restarting the gateway, the same
gateway/exec-approvals path delivers the card cleanly and the user-clicked
button comes back as a structured card action (live log excerpt, same file):

2026-05-09T10:33:30.135Z info  gateway/heartbeat heartbeat: started
2026-05-09T10:33:31.235Z info  gateway/delivery-recovery Recovered delivery 98bd38d1-*** on feishu
2026-05-09T10:33:31.237Z info  gateway/delivery-recovery Delivery recovery complete: 1 recovered, 0 failed
2026-05-09T10:33:37.331Z info  channels/feishu feishu[default]: handling structured card action feishu.payload.button from ou_910c***
2026-05-09T10:33:37.845Z info  channels/feishu feishu[default]: Feishu[default] DM: /approve fa02689f-*** allow-always
2026-05-09T10:33:37.853Z info  channels/feishu feishu[default]: dispatching to agent

Searching the post-restart log window for the V2 schema rejection returns
zero hits:

$ openclaw logs --limit 500 --max-bytes 1500000 --plain | grep -E '230099|cards of schema|unsupported tag action'
(no matches)

The Feishu-side render of the card after the fix shows three buttons —
Allow Once (default), Allow Always (default), Deny (danger) — laid out
in a single horizontal row, matching the original V1 action row visual.

Surface Before After
gateway/exec-approvals deliver 400 / feishu_code 230099 / unsupported tag action success, card visible in chat
feishu_msg_id returned n/a (request rejected) populated, card editable on resolution
Button click round-trip n/a (no card to click) handling structured card action feishu.payload.button → agent receives /approve <id> <decision>

Observed result after fix: Feishu approval cards render with working
Allow Once / Allow Always / Deny buttons in a column_set row; clicking a
button surfaces a feishu.payload.button event in
channels/feishu and the matching /approve <id> <decision> text is
dispatched to the agent. gateway/delivery-recovery no longer retries the
card send. No more feishu_code 230099 / 200861 unsupported tag action
entries appear in the gateway log after restart.

What was not tested:

  • pnpm test extensions/feishu and pnpm tsgo* — no pnpm toolchain on the
    developer host where edits were authored; relying on CI for Vitest/tsgo
    coverage. Test assertions for the four mutated production sites and the
    shared expectSentCardHasP2pAction helper were updated in lockstep with
    the source edits.
  • The new column_set input branch in sanitizeNativeFeishuCardElement is
    exercised only via the legacy tag: "action" back-compat path in the
    existing normalizes caller-supplied native Feishu cards test; a dedicated
    forward-compat case for tag: "column_set" input is not yet added. Happy
    to add one if reviewers want it.

Side note (separate issue worth filing?)

While debugging I also noticed that pending approvals are cleared after expiry or restart (per the APPROVAL_NOT_FOUND_DETAILS.remediation string
in approval-shared). For long-running agents that block on approvals this
means a routine gateway restart silently invalidates every outstanding
approval card the user can still see in their Feishu chat — clicking those
buttons returns unknown or expired approval id. Persisting pending
approvals so they survive restarts would meaningfully improve the UX.
Happy to file that as a separate issue and possibly a follow-up PR if
maintainers agree it's in scope.

Feishu's V2 card schema no longer accepts the V1 `{ tag: "action", actions:
[...] }` wrapper. The Open Platform API now rejects every button-bearing card
with `feishu_code 230099 / sub-code 200861 / "cards of schema V2 no longer
support this capability; ErrorValue: unsupported tag action"`. As a result no
exec/plugin approval card, `/quick`-actions card, or
`buildFeishuCardElementForBlock("buttons")` block can be delivered: the agent
sits forever waiting on an approval the user never sees.

Add a small `actionRowToColumnSet(buttons)` helper in `outbound.ts` that
emits the V2-supported shape — a `column_set` with one auto-width column per
button — and route the four production sites through it:

  - extensions/feishu/src/outbound.ts        (sanitizeNativeFeishuCardElement,
                                              buildFeishuCardElementForBlock)
  - extensions/feishu/src/card-ux-approval.ts (exec approval card)
  - extensions/feishu/src/card-ux-launcher.ts (quick actions card)

`sanitizeNativeFeishuCardElement` keeps accepting legacy V1 input (`tag:
"action"`) for back-compat with callers that still hand-build native cards in
the old format, and now also accepts forward-compat input (`tag:
"column_set"`). It always emits the V2 shape.

Tests adjusted to assert the new shape; the legacy-V1 input branch is still
covered via the `normalizes caller-supplied native Feishu cards` test which
sends `tag: "action"` in and expects `tag: "column_set"` out.

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

clawsweeper Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 3, 2026, 8:47 PM ET / July 4, 2026, 00:47 UTC.

Summary
The PR changes Feishu button-bearing cards from legacy V2-rejected action rows to column_set rows across approval, quick-action, outbound sanitizer, and tests.

PR surface: Source +39, Tests +25. Total +64 across 6 files.

Reproducibility: yes. Current main and v2026.6.11 still build Feishu approval and quick-action rows with tag: "action", matching the Feishu V2 rejection described in the linked issue and PR proof.

Review metrics: 1 noteworthy metric.

  • Feishu card row payload shape: 1 row shape changed: action -> column_set. This is the JSON row shape Feishu receives for approval and quick-action buttons, so refresh and delivery proof matter before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #79824
Summary: This PR is the linked candidate fix for the open Feishu Card JSON 2.0 action container rejection issue.

Members:

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

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

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

Rank-up moves:

  • Refresh or replace the branch on current main while preserving extensions/feishu/src/presentation-card.ts behavior.
  • Run the focused Feishu test command and git diff --check after the refresh.

Proof path suggestion
A real Feishu/Lark approval card render and button callback would materially help after the refresh, and no dedicated Feishu Mantis lane is listed. Mantis is currently scoped to Telegram, Discord, and web UI chat proof, so it is not the right proof path for this surface.
Use maintainer screenshot/manual proof, browser or Playwright proof, Crabbox where appropriate, or normal local artifact proof instead.

Risk before merge

  • [P1] The PR branch is currently dirty against main, so it cannot merge as submitted.
  • [P1] A mechanical refresh could regress the newer presentation-card.ts top-level button renderer if it replays the old outbound sanitizer changes without reconciling current main.
  • [P1] Feishu rejects unsupported button-row shapes before the message reaches chat, so an incorrect refresh can still drop approval or quick-action cards.

Maintainer options:

  1. Refresh the Feishu row-shape fix (recommended)
    Rebase or replace the PR so approval, quick-action, and caller-supplied legacy native-card rows emit a Feishu V2-supported shape while the current presentation-card button path remains intact.
  2. Wait for author refresh
    Keep this useful source PR open but do not merge until the author resolves current-main conflicts and confirms the final Feishu button row still round-trips.
  3. Close only after replacement exists
    If the source branch cannot be refreshed cleanly, close it only after an equivalent replacement PR exists or lands so the canonical Feishu issue is not orphaned.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Refresh the Feishu card V2 fix on current main. Preserve `extensions/feishu/src/presentation-card.ts` and current native presentation-button rendering, convert the remaining approval, quick-action, and caller-supplied native-card legacy `action` rows to a Feishu V2-supported layout, update stale launcher/card helper/native-card tests, and run `node scripts/run-vitest.mjs extensions/feishu/src/outbound.test.ts extensions/feishu/src/bot.card-action.test.ts extensions/feishu/src/card-ux-launcher.test.ts extensions/feishu/src/channel.test.ts` plus `git diff --check`.

Next step before merge

  • [P2] A narrow automated repair can refresh this useful Feishu row-shape fix onto current main because the source PR is maintainer-modifiable and no product decision is needed.

Security
Cleared: The diff changes Feishu card JSON construction and tests only; it adds no dependency, workflow, credential, package, download, or code-execution surface.

Review findings

  • [P2] Refresh against current Feishu card paths — extensions/feishu/src/outbound.ts:152-156
Review details

Best possible solution:

Refresh or replace this branch on current main, keep the fix inside the Feishu plugin card-building boundary, preserve current presentation-card behavior, and validate focused Feishu card tests plus real delivery proof if the final row layout changes.

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

Yes. Current main and v2026.6.11 still build Feishu approval and quick-action rows with tag: "action", matching the Feishu V2 rejection described in the linked issue and PR proof.

Is this the best way to solve the issue?

Yes in direction, but not as submitted. The Feishu plugin card builders and sanitizer are the right boundary, but the branch predates current main’s presentation-card renderer and must be refreshed before merge.

Full review comments:

  • [P2] Refresh against current Feishu card paths — extensions/feishu/src/outbound.ts:152-156
    This branch adds the row-shape helper into the pre-86588 outbound.ts sanitizer/block-helper layout, but current main now has presentation-card.ts rendering buttons as top-level Feishu button elements and a different native-card sanitizer shape. GitHub marks the branch dirty, so refresh the patch against current main and preserve the current presentation-button tests while converting the remaining approval/launcher/native legacy rows.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.9

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P1: The PR targets a broken Feishu approval and quick-action card delivery workflow affecting real users on released builds.
  • merge-risk: 🚨 message-delivery: The diff changes Feishu interactive-card button-row payloads, and an incorrect refresh can drop or misrender approval and quick-action messages.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (logs): The PR body includes redacted before/after Feishu gateway logs and a live button-click round trip from a real tenant using the same action to column_set rewrite.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted before/after Feishu gateway logs and a live button-click round trip from a real tenant using the same action to column_set rewrite.
Evidence reviewed

PR surface:

Source +39, Tests +25. Total +64 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 4 108 69 +39
Tests 2 63 38 +25
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 6 171 107 +64

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs extensions/feishu/src/outbound.test.ts extensions/feishu/src/bot.card-action.test.ts extensions/feishu/src/card-ux-launcher.test.ts extensions/feishu/src/channel.test.ts.
  • [P1] git diff --check.

What I checked:

  • Repository policy read: Root and scoped extension policy were read; plugin-boundary and message-delivery review guidance apply to this bundled Feishu plugin PR. (AGENTS.md:1, f59310a555d7)
  • Live PR state: GitHub reports the PR head 09ecc9e as open, mergeable=false, mergeable_state=dirty, and maintainer_can_modify=true. (09ecc9eba006)
  • PR diff changes Feishu row shape: The PR adds actionRowToColumnSet and routes legacy action rows toward column_set, matching the requested bug-fix direction but against an older outbound shape. (extensions/feishu/src/outbound.ts:152, 09ecc9eba006)
  • Current main approval cards still emit legacy rows: Current main still builds Feishu approval cards with tag: "action", so the central issue is not implemented on main. (extensions/feishu/src/card-ux-approval.ts:41, f59310a555d7)
  • Current main quick-action cards still emit legacy rows: Current main still builds the quick-action launcher row with tag: "action", leaving the reported /quick path unfixed. (extensions/feishu/src/card-ux-launcher.ts:48, f59310a555d7)
  • Sibling presentation path changed on main: Current main renders presentation button blocks as top-level button elements and tests assert no action element for that path, so a refresh must preserve this newer behavior. (extensions/feishu/src/presentation-card.ts:118, f59310a555d7)

Likely related people:

  • Takhoffman: Authored the merged PR that introduced Feishu structured card actions and the approval/launcher card builder files; also appears in the later presentation-button merge metadata as an approver/co-author. (role: feature introducer and adjacent reviewer; confidence: high; commits: fa62231afca3, 170e0aac2a54; files: extensions/feishu/src/card-ux-approval.ts, extensions/feishu/src/card-ux-launcher.ts, extensions/feishu/src/card-action.ts)
  • drvoss: Authored the merged Feishu schema 2.0 config repair in the same interactive approval and launcher card files. (role: adjacent schema repair author; confidence: medium; commits: 6e28bd2eb66e; files: extensions/feishu/src/card-ux-approval.ts, extensions/feishu/src/card-ux-launcher.ts, extensions/feishu/src/send.ts)
  • NianJiuZst: Opened the source PR for the later merged presentation-button rendering path that a refresh of this PR must preserve. (role: adjacent presentation-button contributor; confidence: medium; commits: 170e0aac2a54; files: extensions/feishu/src/presentation-card.ts, extensions/feishu/src/outbound.ts, extensions/feishu/src/channel.test.ts)
  • drobison00: Authored a recent merged Feishu card-action dispatch fix near the same interactive-card callback surface. (role: adjacent card-action contributor; confidence: medium; commits: 90979d7c3ef7; files: extensions/feishu/src/card-action.ts, extensions/feishu/src/bot.card-action.test.ts)
  • joelnishanth: Current-line blame in this checkout points to a recent broad restore-style commit that touched the affected Feishu files, but the feature history points elsewhere. (role: recent file restorer; confidence: low; commits: 97e582ec7400; files: extensions/feishu/src/card-ux-approval.ts, extensions/feishu/src/card-ux-launcher.ts, extensions/feishu/src/outbound.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 (1 earlier review cycle)
  • reviewed 2026-07-03T23:07:48.220Z sha 09ecc9e :: needs changes before merge. :: [P2] Refresh against current Feishu card paths

@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 9, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 9, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 9, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 9, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 1, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 1, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 2, 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: 👀 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. 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. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 14, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 19, 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 merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. 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: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feishu: card V2 schema rejects deprecated action container — exec/plugin approval cards fail with code 230099

1 participant