Skip to content

feishu: card rollover never fires — long tasks silently stop updating #814

Description

@cuipengcx90

Symptom

Long Feishu tasks stop updating their card around step ~50 while the agent keeps running. The user has to send "继续" to get a working card back, and any long task reproduces it (198 occurrences of the card failure in a single session log).

Root cause (two defects)

  1. _rollover_locked() bumps page_no and clears msg_id but not self.steps. _build_locked() iterates the full self.steps, so the "new" card is exactly as large as the overflowing one and fails the same way — rollover is a no-op.

  2. _push_sync() returns (False, False) when the create path fails, while _worker_loop rolls over only on limit=True. Once msg_id is None, every later push takes the create path, returns limit=False, and rollover never runs again — the card becomes permanently unsendable.

Reproduction

Any session where accumulated steps exceed the card size limit (roughly 50 steps at _DETAIL_LIMIT = 4000). The log then repeats:

发送失败: 230099, Failed to create card content, ext=ErrCode: 200800; ErrMsg: create universal card fail;

while 上一张工作卡片达到飞书限制 (the rollover note) never appears — direct proof that rollover never fired.

Suggested fix

  • Clear self.steps in _rollover_locked() and carry turn_base so step numbering stays contiguous.
  • Roll over proactively when steps reaches a threshold, instead of waiting for the API to reject the card.
  • Return limit=True from the create path on failure so the caller can roll over and retry.

Activity

  1. cuipengcx90 commented on Sep 21, 2026

    @cuipengcx90
    Author

    Update: fixed locally in my fork, using button pagination instead of multi-card rollover.

    Approach

    • The card now renders ◀ prev / N/M / next ▶ at the bottom; each button's value carries {ga_card, page}.
    • Registered via register_p2_card_action_trigger; the callback resolves the card instance from a uid registry and re-patches the same message, so the whole step history stays browsable in one card.
    • Pagination uses dual criteria: max 50 steps and 8000 chars per page. Measured from real logs: the original overflow fired around step 58, with single-step detail averaging 272 chars (max 9003), so a step-count-only limit is not enough.

    Both underlying defects are also fixed

    • _rollover_locked() now clears self.steps and advances turn_base, so a rollover page is actually smaller and step numbering stays contiguous.
    • _push_sync() returns limit=True on the create path when it fails, so the caller can roll over and retry instead of leaving msg_id = None forever.

    Result: no more silent card failures, and long tasks no longer need a manual "continue".

    Happy to open a PR if this fits your design — the alternatives (multi-message rollover, or a rolling window that drops old steps) both hurt reviewability.

  2. louisss1016 commented on Oct 1, 2026

    @louisss1016

    I'd like to take this one, scoped to the two defects and the minimal fix suggested in the issue body:

    • _rollover_locked() clears self.steps and carries turn_base so step numbering stays contiguous and the new card is actually smaller;
    • _push_sync() returns limit=True when the create path fails, so _worker_loop rolls over and retries instead of the card becoming permanently unsendable.

    @cuipengcx90 you mentioned a local fork fix using button pagination — I'm deliberately not attempting the pagination rework here, keeping this PR to the rollover defects so the two approaches can coexist (yours is the nicer long-term UX; this unblocks the silent-stop failure mode with a small diff). If you'd rather own the whole thing, say the word and I'll drop this.

  3. louisss1016 commented on Oct 1, 2026

    @louisss1016

    PR up: #820

    Important finding while implementing: the code the issue references (_rollover_locked() / _push_sync() / _worker_loop, _DETAIL_LIMIT = 4000) does not exist on current main. A rollover implementation was merged in #209 (bf002e5b) but was dropped in the later Feishu interface rework (df525560) — main today has no rollover at all, and the original freeze symptom reproduces structurally.

    So this PR re-lands the rollover behavior adapted to the current _TaskCard:

    • _push() returns (ok, limit); the create path reports limit=True on failure (your defect 2 — with msg_id is None every push took the create path and never rolled over)
    • _rollover() clears steps so the continuation card is actually smaller (your defect 1)
    • turn_base + monotonic turn_no keep Turn N contiguous across cards
    • proactive flip at 50 steps — just under your measured ~58-step overflow point

    @cuipengcx90 as agreed, scoped to the rollover defects only; your button-pagination fix is complementary and untouched here.

    11 new tests (ast/exec pattern); full suite 298 passed, same 3 pre-existing Windows-env failures as before my change.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions