Skip to content

feat: post-tool-use monitor + sendPrompt watcher hardening#2

Open
suharvest wants to merge 5 commits intotasict:mainfrom
suharvest:pr/monitor-improvements
Open

feat: post-tool-use monitor + sendPrompt watcher hardening#2
suharvest wants to merge 5 commits intotasict:mainfrom
suharvest:pr/monitor-improvements

Conversation

@suharvest
Copy link
Copy Markdown

Summary

Five related changes that harden background task monitoring and the long-running sendPrompt path:

  • Auto-start Monitor on rescue dispatch via a PostToolUse hook, so users don't have to manually kick off monitoring after firing a background task.
  • sendPrompt: race fetch vs. completion watcher + env-tunable timeouts. The single fixed deadline used to surface as a bare `fetch failed` for long workloads (engine builds, large refactors). Now the watcher keeps polling the session even if the POST times out, and both `OPENCODE_REQUEST_TIMEOUT_MS` / `OPENCODE_PROMPT_TIMEOUT_MS` are tunable.
  • Monitor fixes: inline the result fetch and fix a JS syntax bug in parse-status that blocked progress rendering.
  • `status --json`: honor the flag and support single-task lookup (previously ignored `--json` for narrow queries).
  • Progress surfacing: monitor output now includes activity signals and a heartbeat so users can tell a silent-but-alive task from a stuck one.

Test plan

  • Background `rescue` dispatch auto-starts Monitor; progress + heartbeat render correctly
  • Long task (>5 min) no longer fails with `fetch failed` when the server's POST cap is hit — watcher reconciles completion
  • `status --json` returns a valid JSON payload for both full snapshot and single-task lookup
  • Timeouts override via `OPENCODE_REQUEST_TIMEOUT_MS` / `OPENCODE_PROMPT_TIMEOUT_MS` verified

When Claude dispatches an opencode rescue task (via Agent tool or direct
companion Bash call), this hook detects the new task-xxx id in the tool
response and injects a system-reminder instructing Claude to start a
persistent Monitor covering that id. On terminal states the Monitor
emits a READY line pointing to the companion result command so Claude
fetches the full payload and summarizes it for the user without needing
to be asked.

- New plugins/opencode/scripts/post-tool-use-monitor-hook.mjs
- hooks.json: register PostToolUse (matcher: Agent|Bash, timeout 5s)

Gracefully no-ops on non-matching tool output or missing companion markers.
On terminal state the Monitor script now calls companion result <id> and
emits the truncated summary inline (bounded by OPENCODE_MONITOR_RESULT_CHARS,
default 1500). Claude sees the result summary directly in the Monitor
event and no longer needs a follow-up Bash call.

Also fixes an extra trailing ) in the inline node -e expression that
would have caused the status parser to syntax-error at runtime.
… timeouts

OpenCode server's POST /session/:id/message occasionally fails to close
its HTTP response after the session emits the terminal assistant message
(observed with glm-5 backend, opencode 1.4.x). Without this fix,
sendPrompt hangs until AbortSignal fires, leaving the companion job
stuck in 'investigating' status until the (previously 5 min) timeout.

Changes:
- Race the POST fetch against a /session/:id/message polling watcher;
  whichever returns first aborts the other. Watcher only accepts a
  completion whose info.time.completed >= prompt startedAt.
- Bump generic request() timeout and sendPrompt timeout to 30 min,
  configurable via OPENCODE_REQUEST_TIMEOUT_MS / OPENCODE_PROMPT_TIMEOUT_MS
  env vars.
- Completion poll interval configurable via OPENCODE_COMPLETION_POLL_MS
  (default 5s).
`status` handler was ignoring argv entirely — `--json` was silently
dropped and positional task ids were never matched. Tooling that piped
status through jq would choke on the markdown fallback with "parse
error: Invalid numeric literal".

Now:
- `status --json` emits a workspace snapshot as JSON ({workspaceRoot,
  running, latestFinished, recent})
- `status <tid> [--json]` looks up a single job by id/prefix. JSON
  form is {workspaceRoot, job: <enriched|null>} so callers can always
  read .job.status safely.
- `status --all` widens from session-scoped to all-sessions (useful
  for cross-session observers like monitor scripts)
- Markdown output unchanged for the no-flag case.
Previously the Monitor script only emitted on status/phase transitions.
For long-running tasks that sit in 'running/investigating' for many
minutes, the user saw one initial event and then nothing — no way to
tell if the task was still alive.

Now:
- Include the last line of progressPreview in the state signature so
  any new log activity inside the task triggers an event (with elapsed
  time + latest log snippet)
- Emit a heartbeat every HEARTBEAT_POLLS ticks (default 10 = ~5min)
  with current status/phase/elapsed even when nothing has changed
- Both tunable via OPENCODE_MONITOR_HEARTBEAT_POLLS env var
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.

1 participant