#19615·lobehub

lh agent run timeout policy: quiet active runs under --json and in-flight polling requests

Author: LucaLin233Created Sep 15, 2026Updated Sep 15, 2026

Scope and evidence

This follow-up tracks timeout-policy questions found while reviewing #19597 at 07c31121. These are source-level findings, not an end-to-end reproduction against a deployment.

The new WebSocket progress deadline introduces the quiet-run concern below. The existing JSON error path, fixed polling interval and absence of an explicit request deadline should not all be attributed to this PR.

1. A quiet active run can outlast the WebSocket progress deadline

PROGRESS_TIMEOUT is 300_000 in apps/cli/src/utils/agentStream.ts:183. heartbeat_ack does not reset it (312), and expiry rejects the stream.

In apps/cli/src/commands/agent.ts:489, if (options.json) throw error; bypasses the polling fallback. Consequently, in JSON mode, a run that remains active but produces no deadline-resetting messages for five minutes can cause the CLI to fail without checking the operation status. This does not establish that the server-side run failed.

On failure, the WebSocket helper may print buffered events as a JSON array if any were collected (agentStream.ts:236-245). That array may contain only part of the run's event history; it is not necessarily malformed JSON or a success indication.

2. The policy values are fixed in the reviewed code

  • PROGRESS_TIMEOUT = 300_000agentStream.ts:183
  • HANDSHAKE_TIMEOUT = 30_000agentStream.ts:199
  • POLL_MS = 10_000, UNREADABLE_LIMIT = 3, DEADLINE_MS = 60 * 60_000agent.ts:891-898

The reviewed code exposes no corresponding configuration controls. Whether these should be configurable is a policy question, not proof that every fixed default is defective.

3. The polling deadline does not bound an in-flight query

The loop awaits getOperationStatus.query at agent.ts:909; its elapsed-time check is later, at 966.

The query call and the client's httpLink configuration (apps/cli/src/api/client.ts:73-79) do not explicitly tie the request to the polling deadline. If the query remains pending, that deadline check cannot run. Any lower-level transport timeout is separate from the loop's stated budget.

The new polling tests use resolved query results; they do not cover a query that remains pending.

Proposed direction — for discussion

In the #19597 discussion, L4XB proposed checking getOperationStatus when the WebSocket progress deadline fires, rather than treating inactivity alone as evidence that the run is no longer active.

That proposal still needs decisions about:

  • bounding the status probe itself;
  • handling an active result, including any remaining waiting budget;
  • handling missing, unreadable or failed status probes without calling them successful completion;
  • documenting JSON output and exit behavior on timeout;
  • whether any budgets should be configurable.

Simply increasing the inactivity interval does not resolve the distinction between a quiet active run and a finished run whose end event was not received. Resetting it on heartbeat acknowledgements would also restore the hang in the gateway scenario described by L4XB.

Related

  • #19597 and #19543.
  • This tracks remaining timeout-policy questions, not a new reproduction of the original hang. Whether any part belongs in #19597 or a later PR remains a maintainer decision.