Skip to content

fix(automations): stop headless runs failing at the 5-minute tui-idle default - #22731

Closed
leilei3167 wants to merge 8 commits into
stablyai:mainfrom
leilei3167:fix/issue-22725-headless-tui-idle-timeout
Closed

leilei3167 wants to merge 8 commits into
stablyai:mainfrom
leilei3167:fix/issue-22725-headless-tui-idle-timeout

Conversation

@leilei3167

Copy link
Copy Markdown
Contributor

ELI5

Headless automation runs that take longer than five minutes were being marked as failed with a timeout error, even when the agent was still working and later finished successfully. This change makes the headless path watch for completion the same way other automation runs already do, so a long run can keep going past that five-minute mark.

What Changed

  • Before: the serve-mode headless dispatcher waited for tui-idle with no timeoutMs, so it inherited the runtime's 5-minute default and rejected with timeout while the agent was still busy.
  • After: headless completion goes through the retained-run terminal observer, which re-arms on each tui-idle timeout until the shared 6-hour deadline (or until the agent actually goes idle).
  • Added a regression that documents the bare 5-minute failure and checks that the headless completion helper still completes after an agent works past that window.

Why

The separate completion observer already treated timeout as retryable for up to 6 hours. Headless dispatch did not, so the same long agent run succeeded in one path and failed in the other. Routing headless through that observer is the smallest way to share the retry behavior instead of inventing a second deadline. Alternatives considered: passing a single 6-hour timeoutMs on the bare wait (fixes the cutoff, but skips the observer's reused-pane start edge and the shared "stopped watching after 6h" copy), or rewriting settlement in the larger open automation PRs (out of scope for this bug).

Linked Issue

Fixes #22725

Visual Proof

N/A — headless completion waiting only; no UI or interaction change.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Regression: pnpm exec vitest run --config config/vitest.config.ts src/main/automations/headless-dispatch-terminal-completion.test.ts

  • Bare waitForTerminal({ condition: 'tui-idle' }) rejects with timeout at 5 minutes while the agent is still working (documents the pre-fix failure).
  • Headless completion via the terminal observer still resolves completed after the agent works past 5 minutes and then goes idle.
  • Neighboring observer suite: pnpm exec vitest run --config config/vitest.config.ts src/main/automations/runtime-terminal-run-observer.test.ts — 6/6 passed.
  • Platforms: pure wait/observe logic (macOS / Linux / Windows / SSH share the same runtime wait); no Electron UI run for this PR.

AI Disclosure

Review

Self-review notes:

  • Cross-platform: timeout/re-arm lives in the shared terminal observer; no OS-specific branches added.
  • SSH/remote/local: headless serve-mode path now uses the same observeCompletion retained runs already use on remote hosts.
  • Agent/integration: any agent that reports tui-idle; no agent-specific parsers changed.
  • Performance: same poll/re-arm cadence as existing observation; no new timers beyond that path.
  • Security: no credential, shell, or path execution changes.
  • UI: none.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

No security, cross-platform, Remote SSH, mobile, backwards-compat, or performance concerns beyond the notes above. Desktop (non-serve) dispatch is unchanged.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

… default

Headless dispatch waited for tui-idle with no timeoutMs, so long agent runs
inherited the runtime's 5-minute default and were marked dispatch_failed while
still working. Observe completion through the retained-run terminal observer so
timeouts re-arm until the shared 6h deadline.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one test-coverage nit inline.

Reviewed changes

  • Headless dispatch now waits through the shared observer — the serve-mode headlessDispatcher builds its completion promise from createHeadlessAutomationCompletion instead of a bare waitForTerminal(..., { condition: 'tui-idle' }), so each tui-idle timeout re-arms until the observer's 6h deadline rather than failing at the runtime's 5-minute default.
  • New headless-dispatch-terminal-completion.ts — thin helper that delegates to terminalObserver.observeCompletion with a fresh AbortController signal; the observer owns snapshot capture at the same TERMINAL_SNAPSHOT_LIMIT = 2_000 the removed inline block used.
  • New regression test — one case documents the bare 5-minute wait, the other drives the real observer + helper past that window to completed.

Verification on the checked-out head: typecheck:node EXIT 0, check:code-quality:changed reports 0 new findings, and the two focused suites pass (8 tests).

ℹ️ Nitpicks

  • The new test file re-declares its own FakePane / FakeWaiter / createFakeRuntime (~60 lines) that closely mirror runtime-terminal-run-observer.test.ts. Sharing the harness would keep the two fakes from drifting apart as the runtime's tui-idle satisfaction shape evolves.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8a2dcd6e-5372-4602-8276-136b89699f31

📥 Commits

Reviewing files that changed from the base of the PR and between 970e45c and d5ecb50.

📒 Files selected for processing (2)
  • src/main/automations/headless-dispatch-terminal-completion.test.ts
  • src/main/automations/runtime-terminal-run-observer.test.ts
💤 Files with no reviewable changes (2)
  • src/main/automations/runtime-terminal-run-observer.test.ts
  • src/main/automations/headless-dispatch-terminal-completion.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The headless automation path now uses the terminal completion observer instead of waiting for tui-idle and reading terminal output directly. The observer is attached for both workspace modes. Tests cover observer delegation and completion after the agent remains active beyond the five-minute default timeout. The terminal observer also uses exit status when deciding whether an idle terminal completed successfully or failed dispatch.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to d5ecb

Service shutdown may leave headless completion observations running. Confirm that they are cancelled before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 970e4

The change keeps completion within the existing automation service and improves handling of long-running agents and failed terminal exits. One setup-failure path may leave an observation running after the launch has been marked failed; no new external access or privilege was identified.

Retained concerns

  • Low · reliability · inferred: On a reused workspace, observation starts before workspace display completes. If display fails, the run is marked failed while its uncancellable observation can continue retrying, weakening failure containment on the serve process.
Security review details

Security Blast Radius

  • inferred — The affected completion and waiter lifecycle is within the serve process handling headless automations. No newly exposed external caller or elevated terminal authority was identified.

Trust Boundaries and Controls

  • observed — Terminal observations still reach run persistence through the automation runner and service rather than a new external completion endpoint.

Resilience and Maintainability Implications

  • inferred — A failed reused-workspace setup can leave its already-started observation active. Repeated failures could consume serve-process waiter capacity, but attacker control of that failure and its production frequency are not established.

Hardening Proposals

  • proposed — Keep the observation’s abort controller with the dispatcher and cancel observation if setup fails before the completion promise is handed to the runner.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: preventing headless automation runs from failing at the five-minute tui-idle timeout.
Description check ✅ Passed The description is complete and aligned with the template. It explains the problem, change, rationale, linked issue, testing, compatibility considerations, and checklist status. Visual proof is correc…
Linked Issues check ✅ Passed The PR satisfies the coding objective in #22725. Headless dispatch now routes completion through createRuntimeAutomationRunTerminalObserver. The observer re-arms tui-idle waits after timeout and…
Out of Scope Changes check ✅ Passed The changes stay within #22725. The dispatcher integration, timeout re-arming, exited-terminal handling, and focused tests support correct headless completion. No unrelated change is demonstrated.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: d86b85b1-ee9b-4a73-b008-5ca451f9c466

📥 Commits

Reviewing files that changed from the base of the PR and between 6ae6ed0 and bfadf32.

📒 Files selected for processing (3)
  • src/main/automations/headless-dispatch-terminal-completion.test.ts
  • src/main/automations/headless-dispatch-terminal-completion.ts
  • src/main/startup/main-process-automations.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Start the shared terminal observer as soon as the reused-workspace agent
terminal exists, before awaiting showManagedWorktree, so a fast finish is
not mistaken for leftover idle. Route the unit test through the production
helper instead of a bare wait mock.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one maintainability nit inline.

Reviewed changes

  • Reused-pane race fix — the completion observer is now attached immediately after launchAgentTerminal and before await runtime.showManagedWorktree(...), so a reused pane that returns to idle during that await still records the busy edge instead of being reported as "Automation agent never started after the prompt was submitted."
  • First regression case now exercises production code — it drives createHeadlessAutomationCompletion with a stubbed observeCompletion and asserts delegation with an AbortSignal, replacing the case that only pinned the fake runtime's own behavior.

Verified on the checked-out head: pnpm tc:node EXIT 0, and headless-dispatch-terminal-completion.test.ts + runtime-terminal-run-observer.test.ts pass 8/8.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1e803e3b-e506-4c03-8ec3-3ed27d96c0da

📥 Commits

Reviewing files that changed from the base of the PR and between bfadf32 and 2f6359c.

📒 Files selected for processing (2)
  • src/main/automations/headless-dispatch-terminal-completion.test.ts
  • src/main/startup/main-process-automations.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Attach a no-op rejection handler when creating the headless completion
promise so an early observe failure during showManagedWorktree does not
surface as main_unhandled_rejection before the dispatcher returns.
Also return both workspace modes through one launch-result object.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — re-review of the third commit since the prior pullfrog review.

  • Single launch-result return — the reused-workspace early return and the second completion construction are gone; both workspace modes now flow through one return object with one completion declaration, retiring the earlier "a field added to one return can go missing" nit.
  • Unhandled-rejection guard — a no-op void completion.catch(() => {}) covers the window before the runner attaches its own .then/.catch, so an early observeCompletion failure during await showManagedWorktree cannot log main_unhandled_rejection. The runner's handler still observes the rejection and marks dispatch_failed, so no result is lost.
  • Ordering preserved — the observer still attaches right after launchAgentTerminal and before showManagedWorktree on the reused path; the worktree lookup is now gated by workspaceMode !== 'new_per_run', which matches the previous else (the mode is a two-value union, 'existing' | 'new_per_run').

Verification on the checked-out head: pnpm tc:node EXIT 0; headless-dispatch-terminal-completion.test.ts + runtime-terminal-run-observer.test.ts pass 8/8.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (2)

🟡 Minor · Handle an exited terminal before applying the busy-edge guard. · main-process-automations.ts:32-73

src/main/startup/main-process-automations.ts:32-73
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle an exited terminal before applying the busy-edge guard.

When createManagedWorktree returns after a fast-finished new-per-run terminal, the initial tui-idle probe receives status: 'exited' but the observer discards status and exitCode. It treats the exited terminal as stale idle, waits for a busy edge that cannot occur, and persists dispatch_failed.

Use the re-exported RuntimeTerminalWait type. Skip the busy-edge guard for exited terminals, and classify only exit code 0 as completed.

Suggested fix
 import type { AutomationRunOutputSnapshot } from '../../shared/automations-types'
+import type { RuntimeTerminalWait } from '../../shared/runtime-types'
 
@@
   waitForTerminal(
     handle: string,
     options?: { condition?: 'tui-idle'; timeoutMs?: number; signal?: AbortSignal }
-  ): Promise<{ satisfied: boolean; blockedReason?: string }>
+  ): Promise<RuntimeTerminalWait>
@@
-    return wait.satisfied
+    return wait.satisfied && wait.status !== 'exited'
@@
   runtime: AutomationRunTerminalHost,
   handle: string,
-  wait: { satisfied: boolean; blockedReason?: string }
+  wait: RuntimeTerminalWait
 ): Promise<AutomationRunCompletionObservation> {
   const outputSnapshot = await readTerminalSnapshot(runtime, handle)
-  if (wait.satisfied) {
+  if (wait.satisfied && (wait.status !== 'exited' || wait.exitCode === 0)) {
     return { status: 'completed', outputSnapshot, error: null }
   }
🟡 Minor · Handle reused-workspace lookup failure without orphaning… · main-process-automations.ts:63-85

src/main/startup/main-process-automations.ts:63-85
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle reused-workspace lookup failure without orphaning completion observation.

The runner already records dispatch_failed when the dispatcher rejects. It also attaches the completion handler only after the dispatcher resolves. This behavior existed before this PR, so the missing eventual completion record is not newly introduced.

This PR starts createHeadlessAutomationCompletion before showManagedWorktree. If that lookup rejects, the dispatcher returns dispatch_failed while the completion observer continues without a runner handler or an owned cancellation signal. The observer can continue until completion or its observation deadline.

Make the display-name lookup nonfatal. This lets the dispatcher return the completion promise to the runner. The previously reported cancellation issue does not fix this path by itself because service-stop and watcher cancellation do not run when the dispatcher rejects.

Suggested fix
           if (automation.workspaceMode !== 'new_per_run') {
-            const worktree = await runtime.showManagedWorktree(`id:${workspaceId}`)
-            workspaceDisplayName = worktree.displayName ?? null
+            try {
+              const worktree = await runtime.showManagedWorktree(`id:${workspaceId}`)
+              workspaceDisplayName = worktree.displayName ?? null
+            } catch {
+              // The terminal launch succeeded; the display name is optional.
+            }
           }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: caed81af-44c9-4ee8-85bb-b3947626b028

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6359c and 30f03c1.

📒 Files selected for processing (1)
  • src/main/startup/main-process-automations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/startup/main-process-automations.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Skip the busy-edge guard when the initial tui-idle probe already reports
status exited, and only classify exit code 0 as completed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b6294b59-388b-4248-9324-1362bc3ef2ac

📥 Commits

Reviewing files that changed from the base of the PR and between 30f03c1 and 970e45c.

📒 Files selected for processing (3)
  • src/main/automations/headless-dispatch-terminal-completion.test.ts
  • src/main/automations/runtime-terminal-run-observer.test.ts
  • src/main/automations/runtime-terminal-run-observer.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The narrowed RuntimeTerminalWait return type makes both test fakes non-assignable, so pnpm tc:node — and the pnpm run typecheck CI job (.github/workflows/pr.yml:304) — now fails with TS2322 in both touched test files.

Reviewed changes — re-review of the fourth commit since the prior pullfrog review (30f03c1).

  • Exited terminals now settle as completion — isTuiIdleSatisfiedNow no longer treats a status: 'exited' wait as stale idle, so the busy-edge guard is skipped for a pane that already finished and the observation is classified from the wait result.
  • Non-zero exit codes fail the run — buildObservation reports completed for a satisfied wait only when the terminal is not exited or the exited terminal recorded exitCode === 0; any other exit code yields dispatch_failed.
  • Regression coverage for both exit outcomes — the observer suite gains a completed-on-exit-0 case and a failed-on-exit-1 case, and the fakes now carry status/exitCode through FakeWaitResult.

ℹ️ Exited-during-wait still rejects instead of settling

isTuiIdleSatisfiedNow skips the busy-edge guard only when the initial probe reports status: 'exited'. A terminal that exits while the observer is already awaiting its long tui-idle wait is rejected with terminal_exited (src/main/runtime/orca-runtime-resolve-exit-waiters.ts:30, PTY twin at :80), and the observer rethrows anything that is not a timeout (runtime-terminal-run-observer.ts:187), so that path still lands on dispatch_failed rather than settling on the exit code. Worth confirming whether exited-as-completion is meant to cover only the already-exited probe or the whole run.

Technical details
# Mid-wait terminal exit is not classified by exit code

## Affected sites
- `src/main/automations/runtime-terminal-run-observer.ts:184-189` — the `catch` treats only `timeout` as retryable; `terminal_exited` is rethrown.
- `src/main/runtime/orca-runtime-resolve-exit-waiters.ts:26-34` and `:75-82` — a pending non-`exit` waiter is rejected, not resolved, when the terminal exits.

## Required outcome
- Decide whether an agent that finishes and exits its terminal mid-wait should settle like the already-exited probe case (completed on exit 0, dispatch_failed otherwise) or keep the rejection.

## Open questions for the human
- Is the headless agent terminal expected to stay alive after the agent finishes, making this unreachable in practice?

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — re-review of the fifth commit since the prior pullfrog review (970e45c6).

  • Aligned the test fakes with RuntimeTerminalWait — dropped the unused blockedReason?: string field from both FakeWaitResult declarations (and a trailing blank line in the observer suite). No fake ever set the field, so no coverage is lost, and the fakes are assignable to AutomationRunTerminalHost again.
  • Cleared the prior CI failure — the TS2322 from the narrowed waitForTerminal return type is gone: pnpm tc:node is EXIT 0 on this head, and the two focused suites pass 10/10.

All prior pullfrog feedback is addressed; the remaining exited-during-wait question from the previous review is unchanged and was left as an open question for the author, so it is not re-raised here.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Wire headless terminal observation through AutomationService-owned
AbortControllers so stop() cancels the 6h observer path and skips
persisting dispatch results after shutdown.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

The newest commit (31a88e0) breaks pnpm run typecheck in two places, so .github/workflows/pr.yml's typecheck job is red, and the abort registry it adds can record a fatal main_unhandled_rejection breadcrumb on the exact abort path it introduces.

Reviewed changes — re-review of the sixth commit since the prior pullfrog review (d5ecb50, which was approved).

  • Headless completion can now be aborted on service stop — a new HeadlessCompletionAbortRegistry wraps the serve dispatcher, allocates a per-run AbortController, threads its signal into createHeadlessAutomationCompletion, and AutomationService.stop() calls abortAll().
  • The runner drops an aborted completion — runHeadlessAutomationDispatch skips markDispatchResult when completionAbortSignal is aborted or the completion rejects with request_aborted, leaving the run dispatched for startup reconciliation.
  • Regression test — run-completion-watcher.test.ts captures the dispatcher's completionSignal, asserts it is aborted on stop(), and checks a late completion does not persist a terminal status.

Verification on the checked-out head 31a88e0: the three focused suites pass 21/21, but pnpm tc:node exits 1 with TS2741 in headless-dispatch-runner.ts:56 and TS2339 in run-completion-watcher.test.ts:296; pr.yml:304 runs that same command.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Begin completion abort before dispatch, pass the signal into the wrapped
dispatcher, and swallow completion cleanup rejections after stop().

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

This commit clears both blockers from the previous review — pnpm tc:node now exits 0 and the finally-derived rejection is handled — but the abort-registry wrapper it retains still leaves the Unit tests job red: service.test.ts's headless scheduled-dispatch case now observes pending instead of dispatched.

Reviewed changes — re-review of the seventh commit since the prior pullfrog review (31a88e0).

  • Runner owns the abort lifecycle — HeadlessAutomationDispatchContext gains beginHeadlessCompletionAbort; runHeadlessAutomationDispatch begins the abort before dispatch and passes completionSignal to the dispatcher, satisfying the required field and clearing the TS2741 that failed pnpm tc:node.
  • Registry validates the signal — HeadlessCompletionAbortRegistry.beginRun registers the per-run controller, and wrapDispatcher refuses a dispatch whose completionSignal did not come from beginRun.
  • finally cleanup is handled — trackCompletion appends .catch(() => {}) to the finally-derived promise, so an aborted completion no longer records main_unhandled_rejection.
  • Tests realigned — a typed headlessLaunchOnly helper replaces the inline dispatcher mocks, service-precheck.test.ts supplies beginHeadlessCompletionAbort, and the abort test asserts through a non-null completionSignal.

Verification on the checked-out head e387f24: pnpm tc:node EXIT 0; pnpm exec vitest run --config config/vitest.config.ts src/main/automations → 1 failed / 209 passed (service.test.ts:417).

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/automations/service.ts Resolved

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — re-review of the eighth commit since the prior pullfrog review (e387f24).

  • Test now awaits the dispatched transition — service.test.ts's headless scheduled-dispatch case wraps the status assertion in await vi.waitFor, so the extra microtask hop the abort-registry wrapper adds before runHeadlessAutomationDispatch records dispatched no longer races the assertion. The check still asserts the exact 'dispatched' status, so it still fails if the transition never lands.

Verification on the checked-out head e53ed09: pnpm tc:node EXIT 0; pnpm exec vitest run --config config/vitest.config.ts src/main/automations → 210 passed / 0 failed (previously 1 failed at service.test.ts:417).

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@leilei3167

Copy link
Copy Markdown
Contributor Author

Closing this in favor of the headless completion path that already landed in #25733 and #34096. Thank you for the time.

@leilei3167 leilei3167 closed this Oct 28, 2026
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.

[Bug]: Headless automation runs longer than 5 minutes are marked dispatch_failed with error "timeout"

2 participants