Skip to content

prompt_async endpoint returns 524 timeout behind Cloudflare due to unnecessary stream() wrapper #16180

@InstaxAI

Description

@InstaxAI

Description

The prompt_async endpoint (POST /:sessionID/prompt_async) wraps a fire-and-forget SessionPrompt.prompt() call in Hono's stream() helper. This opens a streaming HTTP response that never writes any data before closing. Behind reverse proxies with first-byte timeouts (Cloudflare's non-configurable 100s timeout on Free/Pro plans), this causes 524 A Timeout Occurred errors — even though the prompt was successfully accepted server-side.

The frontend then shows "Send failed" and reverts the input text, but the message was already processed. This creates a confusing UX where messages appear to fail but actually succeed.

Steps to reproduce

  1. Deploy OpenCode web behind Cloudflare CDN (Free or Pro plan)
  2. Send a message via the web UI
  3. If the streaming response doesn't complete fast enough, Cloudflare returns 524
  4. Frontend shows "Send failed" toast and reverts input to the text box
  5. But the message was actually received and processed by the server

Cloudflare tunnel error log (production)

ERR error="stream canceled by remote with error code 0" connIndex=2
ERR Request failed error="Incoming request ended abruptly: context canceled"
    dest=https://example.com/session/ses_.../command

Root cause

In packages/opencode/src/server/routes/session.ts, the prompt_async handler:

async (c) => {
  c.status(204)
  c.header("Content-Type", "application/json")
  return stream(c, async () => {
    const sessionID = c.req.valid("param").sessionID
    const body = c.req.valid("json")
    SessionPrompt.prompt({ ...body, sessionID })  // fire-and-forget, no await, no stream.write()
  })
},

The stream() wrapper opens a chunked transfer-encoding response but the callback never calls stream.write() — it just fires off the prompt and exits. This creates an empty streaming response that reverse proxies interpret as a stalled connection.

Expected behavior

prompt_async should return a direct 204 No Content response immediately, since it's designed to be fire-and-forget. An atomic HTTP response completes in milliseconds and is not susceptible to proxy timeout issues.

OpenCode version

1.2.17

Operating System

Ubuntu 24.04 (server behind Cloudflare Tunnel)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions