Skip to content

fix: Resolve turbo watch hang with mixed interruptible persistent tasks#12449

Merged
anthonyshew merged 1 commit intomainfrom
shew/investigate-12433
Mar 26, 2026
Merged

fix: Resolve turbo watch hang with mixed interruptible persistent tasks#12449
anthonyshew merged 1 commit intomainfrom
shew/investigate-12433

Conversation

@anthonyshew
Copy link
Copy Markdown
Contributor

Summary

Fixes #12433

turbo watch hangs on startup when the task graph contains both interruptible: true and non-interruptible persistent tasks. Only the first interruptible persistent task starts; everything else is blocked forever.

Root cause: The watch coordinator split tasks into an "interruptible run" (non-persistent + persistent-interruptible) and a "non-interruptible run" (persistent-non-interruptible), gated behind a oneshot channel. Because the interruptible run contained persistent tasks that never complete, the gate never fired.

Fix: In watch mode, persistent tasks are now fire-and-forget in the visitor — the engine callback is sent immediately (so dependency ordering continues) and the executor runs as a detached background task. Child processes remain tracked by the ProcessManager for later stop_tasks() calls. This eliminates the two-run split and oneshot gate entirely.

Changes

  • visitor/mod.rs: For persistent tasks in watch mode, send the callback immediately and spawn the executor as a detached background task
  • watch.rs: Remove persistent_tasks_handle, the two-run split, and the oneshot gate. Add background_stoppers to track PMs from completed runs. Update stop_impacted_tasks to filter out non-interruptible persistent tasks
  • run/mod.rs: Remove create_run_for_interruptible_tasks, create_run_for_non_interruptible_tasks, has_non_interruptible_tasks
  • engine/lib.rs: Remove create_engine_for_interruptible_tasks, create_engine_for_non_interruptible_tasks
  • Regression test: New watch_mixed_persistent_test fixture + watch_mixed_persistent_tasks_all_start test

Testing

To reproduce the original bug locally, create a monorepo with two packages where one has persistent: true, interruptible: true and the other has persistent: true (non-interruptible), then run turbo watch dev. Before this fix, only one app starts. After, both start.

All 12 watch integration tests pass, including the new regression test.

In watch mode, persistent tasks are now fire-and-forget in the visitor:
the engine callback is sent immediately and the executor runs as a
detached background task. This eliminates the two-run split and oneshot
gate that caused the hang when interruptible persistent tasks blocked
the gate from ever firing.

Fixes #12433
@anthonyshew anthonyshew requested a review from a team as a code owner March 26, 2026 06:43
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples-basic-web Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-designsystem-docs Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-gatsby-web Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-kitchensink-blog Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-nonmonorepo Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-svelte-web Building Building Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-tailwind-web Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
examples-vite-web Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
turbo-site Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am
turborepo-agents Ready Ready Preview, Comment, Open in v0 Mar 26, 2026 6:44am

@anthonyshew anthonyshew requested review from tknickman and removed request for a team March 26, 2026 06:43
@anthonyshew anthonyshew merged commit 326532d into main Mar 26, 2026
58 checks passed
@anthonyshew anthonyshew deleted the shew/investigate-12433 branch March 26, 2026 13:51
github-actions Bot added a commit that referenced this pull request Mar 26, 2026
## Release v2.8.21-canary.12

Versioned docs: https://v2-8-21-canary-12.turborepo.dev

### Changes

- release(turborepo): 2.8.21-canary.11 (#12450) (`b14aa0b`)
- fix: Resolve turbo watch hang with mixed interruptible persistent
tasks (#12449) (`326532d`)

Co-authored-by: Turbobot <turbobot@vercel.com>
github-actions Bot added a commit that referenced this pull request Mar 28, 2026
## Release v2.8.21

Versioned docs: https://v2-8-21.turborepo.dev

### Changes

- release(turborepo): 2.8.20 (#12396) (`45230ec`)
- fix: Disable husky hooks in `update-examples` workflow (#12397)
(`56b79ff`)
- docs: Add link to Docker guide in prune --docker flag section (#12401)
(`e7f0db7`)
- feat: Add `global` configuration key behind
`futureFlags.globalConfiguration` (#12399) (`5f190cf`)
- chore: Update CODEOWNERS to remove /docs owner (#12402) (`3233d3a`)
- fix: Strip JSX components from heading anchors and TOC entries
(#12404) (`3abe553`)
- fix: Move docs app icons into app/ directory (#12403) (`ddf3918`)
- feat: Add experimental structured logging with `--json` and
`--log-file` flags (#12405) (`7ca0601`)
- release(turborepo): 2.8.21-canary.1 (#12407) (`adebb95`)
- docs: Downgrade Next.js (#12408) (`281e89b`)
- chore: Deprecate the `turbo scan` command (#12406) (`4a12c26`)
- release(turborepo): 2.8.21-canary.2 (#12409) (`b9ef212`)
- fix(eslint-plugin-turbo): Guard against missing tasks/pipeline in
forEachTaskDef (#12411) (`6c107c2`)
- release(turborepo): 2.8.21-canary.3 (#12413) (`a2e6635`)
- chore: Upgrade Next.js (#12415) (`b9e6174`)
- Revert "fix: Flush stale mouse tracking events from stdin during TUI
cleanup" (#12416) (`646b06e`)
- fix: Add NixOS environment variables to default passthroughs (#12417)
(`4f12c69`)
- release(turborepo): 2.8.21-canary.4 (#12419) (`19cb539`)
- fix: Resolve security vulnerabilities in `tar` and `rustls-webpki`
(#12418) (`f09b138`)
- release(turborepo): 2.8.21-canary.5 (#12420) (`8aca047`)
- docs: Promote `turbo query` from experimental to stable (#12421)
(`0692aba`)
- docs: Clarify `turbo-ignore`'s future (#12422) (`c5a8235`)
- release(turborepo): 2.8.21-canary.6 (#12423) (`3ebf536`)
- feat: Rework turbo ls to use query internals and add turbo query ls
shorthand (#12424) (`84fd6e3`)
- docs: Clarify environment variables across packages dependency
behavior (#12390) (`e44b0d8`)
- docs: Expand subpath imports example (#12412) (`a7fec57`)
- fix(examples): Update of `with-svelte` example (#11952) (`41d1b2e`)
- release(turborepo): 2.8.21-canary.7 (#12425) (`7155a67`)
- fix: Preserve source dependencies when adding workspace deps in
`turbo-gen` (#11935) (`01c56cc`)
- docs: Add Git history requirements to `turbo query affected` docs
(#12426) (`edc16d5`)
- fix: Prevent horizontal overflow from long inline code on narrow
viewports (#12428) (`a5d641b`)
- release(turborepo): 2.8.21-canary.8 (#12429) (`46814d0`)
- feat: Send git SHA and dirty hash to remote cache (#12427) (`192034a`)
- fix: Upgrade tokio to 1.47.1+ to fix pidfd_reaper panic (#12431)
(`8c25d47`)
- release(turborepo): 2.8.21-canary.9 (#12432) (`2e2f8c3`)
- fix: Use script-shell=bash for cross-platform with-shell-commands
example (#12436) (`d5c2192`)
- docs: Add AI guide to sidebar navigation (#12438) (`021d288`)
- docs: Move `experimentalObservability` into `futureFlags` section
(#12439) (`85812cc`)
- fix: Skip Unix domain sockets and other special files during file
hashing (#12445) (`eb8f75e`)
- fix: Preserve dedupePeers and unknown pnpm lockfile settings (#12443)
(`1529b92`)
- release(turborepo): 2.8.21-canary.10 (#12446) (`014111c`)
- fix: Align dry run cache status with normal run by checking caching
guards (#12448) (`48aa171`)
- release(turborepo): 2.8.21-canary.11 (#12450) (`b14aa0b`)
- fix: Resolve turbo watch hang with mixed interruptible persistent
tasks (#12449) (`326532d`)
- release(turborepo): 2.8.21-canary.12 (#12451) (`379d47b`)
- fix: Avoid `setsid()` in PTY spawn to prevent macOS Gatekeeper CPU
spikes (#12452) (`dcc9f6a`)
- release(turborepo): 2.8.21-canary.13 (#12453) (`19f46e6`)
- feat: Add `packagesFromLockfile()` NAPI binding to `@turbo/repository`
(#12454) (`c58ee79`)
- release(library): 0.0.1-canary.21 (#12455) (`3637185`)
- release(turborepo): 2.8.21-canary.14 (#12456) (`3f87769`)
- refactor: Move cache hit SHA context to verbose logging (#12435)
(`23c15b4`)
- release(turborepo): 2.8.21-canary.15 (#12457) (`6353482`)
- docs: Add missing --force flag documentation (#12440) (`e3b89b0`)
- fix: Prevent panic in turbo watch with persistent tasks (#12459)
(`337b2e8`)
- release(turborepo): 2.8.21-canary.16 (#12461) (`e79a56b`)
- fix: Support `turbo watch` in single-package workspaces (#12460)
(`ae78ce1`)
- release(turborepo): 2.8.21-canary.17 (#12463) (`0bafae2`)
- fix: Missing deps after npm lockfile parsing (#12464) (`fe5a86e`)
- release(turborepo): 2.8.21-canary.18 (#12465) (`c014134`)
- docs: Add AI agent detection and automatic markdown rewrites (#12462)
(`50bd872`)
- fix: Resolve generator name conflicts across workspaces (#12467)
(`d5d37a8`)
- release(turborepo): 2.8.21-canary.19 (#12468) (`7552e93`)
- fix: Remove root package.json from `--affected` global triggers
(#12469) (`91ebb97`)
- release(turborepo): 2.8.21-canary.20 (#12470) (`c5a4690`)
- fix: Show run summary after TUI exits (#12471) (`ffa47d1`)

---------

Co-authored-by: Turbobot <turbobot@vercel.com>
anthonyshew added a commit that referenced this pull request Mar 31, 2026
…file changes

The fire-and-forget change (PR #12449) moved the TaskTracker into a
detached tokio::spawn for persistent tasks. Since persistent processes
run forever, the tracker's sender clone was never dropped, causing
ExecutionTracker::finish() to block indefinitely. This prevented
Run::run() from returning, which starved the watch loop of the ability
to process any subsequent file-change events.

Fix: use a no-op TaskTracker (with a throwaway channel) for fire-and-
forget persistent tasks so the real execution tracker can complete.

Also adds debug! traces at key decision points in the watch loop to
make future watch-mode issues easier to diagnose.

Closes #12505
anthonyshew added a commit that referenced this pull request Mar 31, 2026
…n file changes (#12509)

## Summary

Fixes #12505

- **Cause**: The fire-and-forget change (PR #12449) moved a
`TaskTracker` into a detached `tokio::spawn` for persistent tasks. Since
persistent processes run forever, the tracker's `mpsc::Sender` clone is
never dropped, which blocks `ExecutionTracker::finish()` indefinitely.
This prevents `Run::run()` from returning, starving the watch loop of
the ability to process any subsequent file-change events.
- **Fix**: Use a no-op `TaskTracker` (throwaway channel) for
fire-and-forget persistent tasks so the real execution tracker can
complete.
- **Observability**: Adds `debug!` traces at key decision points in the
watch loop (event receipt, run processing, task stopping, run
completion) to make future watch-mode issues diagnosable with `-vv`.
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.

turbo watch hangs when running multiple persistent tasks with mixed interruptible settings

1 participant