Skip to content

Converge action and gateway run-execution timeout/activity tracking #1069

Description

@marcusrbrown

Summary

The action and the gateway each implement their own run-execution timeout/activity tracking, with no shared code. They've drifted, and #1055 widened the surface by adding a gateway-local inactivity timer that mirrors — but doesn't reuse — the action's pattern. Worth extracting a shared activity-timeout primitive so the two paths stop diverging.

Current state (two parallel implementations)

  • Action (src/features/agent/): a poll watchdog + ActivityTrackerINITIAL_ACTIVITY_TIMEOUT_MS (90s startup-stall guard) in session-poll.ts, event-driven activity marking in streaming.ts, a 30-min hard wall-clock cap (DEFAULT_TIMEOUT_MS) in execution.ts. Completion keyed off session.idle.
  • Gateway (packages/gateway/src/execute/): after Gateway operator runs can timeout after emitting valid output #1055, a resettable inactivity timer (GATEWAY_RUN_INACTIVITY_TIMEOUT_MS, 5 min) reset on every output event, paused during approval waits, combined with a 30-min hard ceiling (GATEWAY_RUN_TIMEOUT_MS) via AbortSignal.any, in run-core.ts. Completion keyed off session.idle.

Both consume the same OpenCode SSE event shapes and both center on session.idle, but the timeout machinery is duplicated. run-core.ts even carries a comment that it deliberately re-implements streaming.ts accessors locally because of a layer/import ban — so the divergence is partly structural.

Why it matters

  • Two timeout models means two places to fix a timing bug, and they already disagree (the gateway shipped a pure 10-min wall clock for months while the action used 30-min + activity awareness — that gap was Gateway operator runs can timeout after emitting valid output #1055).
  • The gateway's inactivity model is now arguably the better one; the action could adopt it.

Possible direction (to evaluate, not yet decided)

  • Extract a small, dependency-free activity-timeout primitive (an inactivity timer with arm/reset/clear + a hard-ceiling composite signal, keyed off a generic "activity" signal) into a shared low-layer module both paths can import, respecting the existing layer rules. Keep session.idle-as-success and approval-pause semantics as caller concerns.
  • Reconcile the constants (startup-stall window, inactivity window, hard ceiling) so the two surfaces have a documented, consistent policy.

Constraints

  • The import-direction ban that forced run-core.ts to re-implement streaming.ts accessors must be respected — the shared primitive has to live at a layer both can depend on.
  • No behavior change to the action path without its own tests; this is a refactor-for-convergence, so it needs to be behavior-preserving on both sides and verified independently.

Context

Split out from #1055, which added the gateway inactivity timer. Low priority — both paths work; this is about not maintaining two drifting copies.

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