Skip to content

fix(tools): make watch_task poll until terminal state - #571

Merged
jonwiggins merged 1 commit into
mainfrom
fix/watch-task-polling
Aug 8, 2026
Merged

fix(tools): make watch_task poll until terminal state#571
jonwiggins merged 1 commit into
mainfrom
fix/watch-task-polling

Conversation

@jonwiggins

Copy link
Copy Markdown
Owner

Summary

watch_task is advertised to the Optio assistant as a long-running watch that polls a task until it reaches a terminal state, honoring pollIntervalSeconds and timeoutMinutes. In reality the executor did a single app.inject to GET /api/tasks/:id and returned an immediate one-shot snapshot — the poll/timeout params were serialized into a query string that the route ignores.

TERMINAL_TASK_STATES was even exported from packages/shared/src/optio-tools.ts with the comment "for the watch_task tool", but it was dead code — referenced nowhere. The polling was never implemented.

This is the real-poll fix (the preferred option in #547), not a description downgrade.

Changes

  • apps/api/src/services/optio-tool-executor.ts: new exported watchTask helper that polls GET /api/tasks/:id every pollIntervalSeconds (clamped to [2s, 60s]) until the task's state is in TERMINAL_TASK_STATES or timeoutMinutes elapses (clamped to [1m, 30m] — never blocks longer than the 30m hard cap).
    • Returns the final observed task detail on terminal ({ watch: "terminal", state, polls, task }).
    • On timeout returns the last-observed state clearly flagged ({ watch: "timeout", message: "Task still <state> after <n> minute timeout", ... }), reported as a normal (non-error) result so the assistant can relay it.
    • An already-terminal task returns immediately after one poll.
    • A non-2xx lookup (e.g. 404) is surfaced immediately — polling won't fix it.
    • executeToolCall now dispatches watch_task to this poller instead of the generic one-shot inject path.
    • Timing is injectable (sleep/now) so tests drive the loop deterministically with no real waits.
  • packages/shared/src/optio-tools.ts: lowered the timeoutMinutes schema maximum from 60 to 30 to match the executor's hard cap (removes contract drift). TERMINAL_TASK_STATES is now actually used.
  • apps/api/src/services/optio-tool-executor.test.ts: added an 11-case watchTask suite — waits through non-terminal states then returns terminal; already-terminal returns immediately; timeout returns last-observed state; poll interval respected and clamped (min/max); non-2xx surfaced immediately; missing id rejected without an API call; dispatch through executeToolCall.

Testing

  • cd apps/api && npx tsc --noEmit — clean
  • pnpm turbo typecheck — 12/12 pass
  • cd packages/shared && npx vitest run — 440 pass
  • cd apps/api && npx vitest run — 2177 pass (executor suite now 23 tests)
  • pnpm format:check — clean

Fixes #547

watch_task advertised a poll-until-terminal watch with pollIntervalSeconds
and timeoutMinutes controls, but the executor did a single app.inject and
returned an immediate snapshot. TERMINAL_TASK_STATES was exported "for the
watch_task tool" yet referenced nowhere — the polling was never implemented.

Add a dedicated watchTask poller in optio-tool-executor and dispatch to it
from executeToolCall. It polls GET /api/tasks/:id every pollIntervalSeconds
(clamped to [2s, 60s]) until the task reaches a TERMINAL_TASK_STATES value
or timeoutMinutes elapses (clamped to [1m, 30m]). Returns the final observed
state on success; on timeout returns the last-observed state clearly flagged
as "still <state> after timeout". A non-2xx lookup is surfaced immediately.

Also lower the schema timeoutMinutes max to 30 to match the executor cap.

Fixes #547
@jonwiggins
jonwiggins merged commit 2431635 into main Aug 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

watch_task returns a snapshot instead of waiting for terminal task state

1 participant