Skip to content

fix(line): retry inbound media download while LINE reports 202 (still preparing)#108351

Merged
steipete merged 3 commits into
openclaw:mainfrom
edenfunf:fix/line-inbound-media-202
Jul 16, 2026
Merged

fix(line): retry inbound media download while LINE reports 202 (still preparing)#108351
steipete merged 3 commits into
openclaw:mainfrom
edenfunf:fix/line-inbound-media-202

Conversation

@edenfunf

@edenfunf edenfunf commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

LINE can answer GET /v2/bot/message/{messageId}/content with 202 Accepted while inbound media is still being prepared. OpenClaw previously passed that empty response to the media store and could persist a 0-byte attachment. This fixes the remaining download half of #96163; #96403 handled filename/classification.

Why This Is the Right Fix

LINE documents 202 on the content endpoint as the not-ready signal. The plugin now polls that endpoint directly, retries only 202, and uses capped exponential backoff: 500ms, 1s, 2s, 4s, 4s; six total attempts. A 15-second readiness deadline aborts a hung request or backoff. Redirects are rejected so the bearer token cannot be forwarded to another origin.

The plugin SDK runtime-fetch path is intentional. @line/bot-sdk 11.1.0 wraps the response reader in a Node Readable without a destroy hook, so destroying that wrapper does not cancel the underlying web stream. This implementation uses the dispatcher-aware plugin SDK fetch wrapper, cancels every discarded 202 or error response, converts the accepted 200 body with Readable.fromWeb, and destroys/awaits that stream if the media store rejects it.

No plugin SDK API changes. The exported downloadLineMedia signature and plugin SDK imports remain unchanged; the generated SDK baseline and plugin-boundary checks pass.

User Impact

Media sent during LINE's preparation window is retried and delivered instead of becoming a 0-byte attachment. Exhausted, failed, or hung downloads surface through the existing media-unavailable path instead of leaking response bodies or silently saving empty content.

Evidence

Contributor live LINE evidence, gathered with the production download path (message IDs redacted):

Still preparing:
  t+0 content request -> HTTP 202, 0 bytes
  retry              -> 121252 bytes written, elapsed 956ms

Ready immediately:
  t+0 content request -> HTTP 200
  download            -> 3236 bytes written, elapsed 399ms

Maintainer proof (live credentials unavailable; mocked recorded sequences accepted for this path):

node scripts/run-vitest.mjs extensions/line/src/download.test.ts
  10 passed

node scripts/check-changed.mjs -- extensions/line/src/download.ts extensions/line/src/download.test.ts
  Blacksmith Testbox exit 0
  format, SDK baseline/boundaries, extension prod/test types, lint,
  media-download guard, and import-cycle checks passed

Regression coverage includes 202 -> 202 -> 200, six exhausted 202 responses, non-202 errors, a hung request, downstream media-store rejection, exact cancellation of every unconsumed body, encoded message IDs, and the full backoff schedule.

Official contract: https://developers.line.biz/en/reference/messaging-api/#get-content and https://developers.line.biz/en/docs/messaging-api/retrying-api-request/

Fixes #96163

@clawsweeper

clawsweeper Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 15, 2026, 11:40 PM ET / July 16, 2026, 03:40 UTC.

Summary
Retries LINE inbound media retrieval with capped exponential backoff while the content endpoint returns HTTP 202, with regression coverage for readiness and exhaustion behavior.

PR surface: Source +35, Tests +56. Total +91 across 2 files.

Reproducibility: yes. The contributor reproduced the current single-request path against a real LINE channel, observed HTTP 202 with an empty body, and showed the patched runtime saving the complete media after retry.

Review metrics: 1 noteworthy metric.

  • Media readiness retry budget: 6 attempts; 11.5 seconds maximum scheduled backoff. This bounds recovery from LINE's documented preparation state while defining the maximum added inbound-media latency.

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

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

Rank-up moves:

  • Refresh the branch against current main and rerun the focused LINE download test before landing.

Risk before merge

  • [P1] The branch is behind current main and needs a review refresh before landing, although no concrete three-way-merge regression is shown.
  • [P1] The 11.5-second worst-case backoff budget is unit-tested, while the supplied live LINE evidence observed readiness after about 956ms rather than exercising exhaustion in production.

Maintainer options:

  1. Decide the mitigation before merge
    Land the plugin-local status-aware retry after refreshing it against current main and retaining both the immediate-success and bounded-exhaustion regression tests.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] The open PR already contains the focused implementation and proof; the remaining action is ordinary maintainer review and landing rather than a separate repair job.

Security
Cleared: The patch adds no dependency, credential, permission, artifact-download, or supply-chain surface and only retries the existing authenticated LINE content endpoint.

Review details

Best possible solution:

Land the plugin-local status-aware retry after refreshing it against current main and retaining both the immediate-success and bounded-exhaustion regression tests.

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

Yes. The contributor reproduced the current single-request path against a real LINE channel, observed HTTP 202 with an empty body, and showed the patched runtime saving the complete media after retry.

Is this the best way to solve the issue?

Yes. Inspecting the documented content-endpoint status at the LINE plugin boundary is narrower and more reliable than adding a new core contract or relying on the transcoding-status endpoint.

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add P1: The PR addresses silently lost LINE media in an active channel workflow, with a real LINE reproduction and direct user-message impact.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR provides redacted after-fix output from a real LINE channel showing an initial HTTP 202 becoming a complete saved file, plus an immediate-ready control case.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.

Label justifications:

  • P1: The PR addresses silently lost LINE media in an active channel workflow, with a real LINE reproduction and direct user-message impact.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR provides redacted after-fix output from a real LINE channel showing an initial HTTP 202 becoming a complete saved file, plus an immediate-ready control case.
Evidence reviewed

PR surface:

Source +35, Tests +56. Total +91 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 37 2 +35
Tests 1 59 3 +56
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 96 5 +91

What I checked:

Likely related people:

  • zaidazmi: Authored the merged filename-preservation change through the same LINE download and media-storage path, including adjacent tests and dependency inspection. (role: recent area contributor; confidence: high; commits: aa5ec51af008; files: extensions/line/src/download.ts, extensions/line/src/download.test.ts, extensions/line/src/bot-handlers.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.

@steipete
steipete force-pushed the fix/line-inbound-media-202 branch from b581ef7 to 93d0a8a Compare July 16, 2026 08:12
@steipete
steipete force-pushed the fix/line-inbound-media-202 branch from 93d0a8a to 39f0a34 Compare July 16, 2026 08:13
edenfunf and others added 3 commits July 16, 2026 09:36
LINE answers the content endpoint with 202 and an empty body for a
short window while it prepares inbound media. The previous single-shot
download saved that empty body as a 0-byte file, silently dropping the
user's media. Poll the content endpoint with capped exponential
backoff until it stops returning 202, releasing each empty response
before retrying, and throw a visible error if it never becomes ready.
@steipete
steipete force-pushed the fix/line-inbound-media-202 branch from 39f0a34 to e7dd96f Compare July 16, 2026 08:36
@steipete
steipete merged commit aea825f into openclaw:main Jul 16, 2026
90 checks passed
@steipete

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 17, 2026
… preparing) (openclaw#108351)

* fix(line): retry inbound media download while LINE reports 202

LINE answers the content endpoint with 202 and an empty body for a
short window while it prepares inbound media. The previous single-shot
download saved that empty body as a 0-byte file, silently dropping the
user's media. Poll the content endpoint with capped exponential
backoff until it stops returning 202, releasing each empty response
before retrying, and throw a visible error if it never becomes ready.

* fix(line): clean up media retry responses

* fix(line): use plugin runtime fetch seam

---------

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: line Channel integration: line size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: LINE: file-type audio attachments not transcribed (0-byte download + missed audio detection)

2 participants