Skip to content

fix: Show toolchain tasks in the TUI and never run in silence - #13308

Merged
anthonyshew merged 3 commits into
mainfrom
shew/tui-toolchain-tasks
Jul 7, 2026
Merged

anthonyshew merged 3 commits into
mainfrom
shew/tui-toolchain-tasks

Conversation

@anthonyshew

Copy link
Copy Markdown
Contributor

Why

In a Cargo-enabled repo (futureFlags.experimentalCargoWorkspaces), running through the default TUI produced a blank, idle screen — not even the JS tasks rendered — while --ui=stream worked fine. Two stacked bugs:

  1. The TUI's task list (tasks_with_command) came from a package.json scripts lookup, so Cargo tasks were never registered.
  2. Far worse: lifecycle events for those unregistered tasks made the App handlers return TaskNotFound, and the ? in the event loop killed the entire TUI mid-run — after the terminal sink had already been disabled for TUI startup. Nothing re-enabled it until the run finished, so every task's output, JS included, silently vanished.

What

  • tasks_with_command now asks the package's toolchain via Toolchain::defines_task — the same authority execution uses (and the same pattern the engine builder already uses for hashing), resolving the standing TODO. JS behavior is bit-identical; Cargo tasks come from the verb tables. Watch mode's task list is fixed by the same change.
  • start_terminal_ui wraps the render-thread handle in a watchdog that re-enables the terminal sink the moment the TUI exits, however it exits (normal shutdown, render error, panic). The invariant it establishes: task output always has a live sink. Both run and watch inherit it because it lives inside start_ui — no caller can forget it.

Deliberately not included: swallowing TaskNotFound events in the TUI. A task-list/event mismatch stays loud (TUI dies, bug gets reported) — but now it degrades to streamed output instead of silence.

How

  • Toolchain resolution mirrors turborepo-engine's definitions.rs (package_infotoolchains().get(&info.toolchain)defines_task); the "proxy" special case is preserved.
  • The watchdog owns the render-error logging (run.rs/watch.rs previously duplicated it); the handle type in UIResult becomes JoinHandle<()>.
  • New engine test builds a real mixed graph (JS packages via discovery + a minimal Cargo workspace discovered through cargo metadata) and asserts: JS task with script ✓, JS task without script ✗, cargo#test ✓, binary crate build ✓.
  • One behavior change to be aware of: pure-Rust repos previously never got the TUI (empty task list → stream fallback); now they do.

To verify manually: turbo build in this repo with the default UI — Cargo task panes should appear with live output (the repro from the original report).

Two fixes for TUI behavior in Cargo-enabled repos:

The TUI task list came from a package.json scripts lookup, so Cargo
tasks were never registered (resolving the standing TODO in
tasks_with_command). Ask the package's toolchain via
Toolchain::defines_task instead - the same authority execution uses -
so the TUI, display, and execution cannot drift. JS behavior is
unchanged (its defines_task is the scripts lookup). This also fixes
the task list in watch mode.

Worse than the missing panes: events for those unknown tasks killed
the TUI event loop mid-run via TaskNotFound, after the terminal sink
had already been disabled - the rest of the run executed with no live
sink and every task's output silently vanished. Wrap the render-thread
handle in a watchdog inside start_terminal_ui that re-enables the
terminal sink the moment the TUI is gone, however it exits (normal
shutdown, render error, panic). Both run and watch inherit it, and
neither caller can forget it.
@anthonyshew
anthonyshew requested a review from a team as a code owner July 7, 2026 17:32
@anthonyshew
anthonyshew requested review from tknickman and removed request for a team July 7, 2026 17:32
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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 Jul 7, 2026 8:30pm
examples-designsystem-docs Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-gatsby-web Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-kitchensink-blog Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-nonmonorepo Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-svelte-web Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-tailwind-web Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
examples-vite-web Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
turbo-site Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 8:30pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
turborepo-eve-agent Skipped Skipped Open in v0 Jul 7, 2026 8:30pm

…3309)

## Why

In a Cargo-enabled repo, the TUI showed a column of `»` running tasks
whose processes didn't exist. The running marker fired at the very top
of task execution — before the cache check and before the serial-group
lock. For JS tasks that gap is milliseconds and invisible; Cargo's
serial group (one cargo at a time, because they'd fight over the build
directory) stretched it to minutes of tasks claiming to run while
queued.

## What

`task_output.start()` moves to immediately before the process spawn,
after serial-group acquisition. States now tell the truth:

| Situation | Before | After |
|---|---|---|
| Waiting on serial group | `»` running, empty pane | pending — same as
waiting on a dependency |
| Holding the lock / executing | `»` | `»` |
| Cache hit | brief false `»` flash | pending → ✓ directly (it never
ran; duration truthfully ~0) |
| JS task wrapping cargo (e.g. napi builds) | unchanged | unchanged —
its process is real, and cargo self-reports "Blocking waiting for file
lock" |

## How

Two consequences the move forces, both handled:

- **Cache hits never start**, so `finish_task` learns the planned →
finished transition (`start().finish()` back to back). Unknown task
names remain a hard error — this is a legitimate state transition, not
event-swallowing. Persistence on TUI exit is unaffected:
`tasks_started()` reads the finished/running lists, which now include
these tasks.
- **`StartTask` was the only carrier of the task's `outputLogs`
setting.** A never-started cache hit would have defaulted to full log
persistence, ignoring `hash-only`/`new-only`. `Event::Status` — which
every cache-restore path already emits before any start — now carries
`output_logs` too, threaded through `TaskSender::status` → `UISender` →
`TuiSender`.

Exit-path audit: the only `execute_inner` return remaining before the
new start location is the cache-hit return (verified — no `?` operators
in between), and spawn-failure paths sit after the start call, keeping
start→finish ordering.

`start()` is a no-op outside the TUI, so stream/grouped log modes are
untouched (their "cache miss, executing" lines come from the run cache,
not this signal).

Tests: `finish_task` planned→finished (+ unknown-name error +
`tasks_started` inclusion), `set_status` delivering `output_logs`;
existing UI/executor/run-cache/cargo-workspace suites green.

Follow-ups deliberately not in this PR: run-summary durations still
include serial-group wait (`tracker.start()` unmoved); batching
contended cargo invocations into one process remains a parked design
discussion.
@vercel
vercel Bot temporarily deployed to Preview – turborepo-eve-agent July 7, 2026 20:29 Inactive
@anthonyshew
anthonyshew merged commit d7622b6 into main Jul 7, 2026
57 of 59 checks passed
@anthonyshew
anthonyshew deleted the shew/tui-toolchain-tasks branch July 7, 2026 20:45
anthonyshew pushed a commit that referenced this pull request Jul 9, 2026
## Release v2.10.5-canary.4

> [!CAUTION]
> Versioned docs aliasing FAILED. [View
logs](https://github.com/vercel/turborepo/actions/runs/29034517569)

### Changes

- release(turborepo): 2.10.5-canary.3 (#13302) (`7e44a29`)
- ci: Authenticate to Remote Cache via OIDC token exchange (#13303)
(`a86839c`)
- fix: Keep pnpm patches with version range keys during prune (#13307)
(`3c27a89`)
- test: Scrub ambient turbo configuration from integration test children
(#13306) (`b03d0d6`)
- fix: Show toolchain tasks in the TUI and never run in silence (#13308)
(`d7622b6`)
- ci: Fix change detection on push events (#13304) (`eec3d61`)
- refactor: Rename the Cargo toolchain id to rust (#13311) (`a548b02`)
- fix: Remove extraneous bun.lock entries during prune (#13317)
(`382e9f5`)
- feat: Require a user-declared name for the Cargo workspace package
(#13312) (`5f01746`)
- feat: Parse and validate the task command field (#13313) (`389ea49`)
- chore: Harden turbo-vsc to invoke turbo without a shell (#13319)
(`4a19b6e`)
- feat: Resolve and execute task command overrides (#13315) (`a549baa`)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
anthonyshew pushed a commit that referenced this pull request Jul 13, 2026
## Release v2.10.5

> [!CAUTION]
> Versioned docs aliasing FAILED. [View
logs](https://github.com/vercel/turborepo/actions/runs/29267191097)

### Changes

- perf: Evaluate simple include globs without wax compilation (#13285)
(`189897a`)
- chore: Dogfood native Cargo support in this repository (#13283)
(`42f067b`)
- release(turborepo): 2.10.4 (#13286) (`f2fce38`)
- ci: Remove dead sccache configuration (#13289) (`558df3f`)
- feat: Serve the Remote Cache as an sccache backend for Cargo tasks
(#13288) (`0d9803f`)
- fix: Reject output path traversal (#13290) (`733ccca`)
- fix: Disable the sccache proxy when remote cache use is off (#13291)
(`3249e22`)
- feat: Embed sccache so the Cargo compile cache needs no installation
(#13293) (`b6d0035`)
- release(turborepo): 2.10.5-canary.1 (#13294) (`e6cd498`)
- fix: Pin a flag-aware turbo canary for the eve-agent deployment
(#13295) (`e60a4bd`)
- fix: Make the sccache compile cache actually cache (#13296)
(`2ac099c`)
- release(turborepo): 2.10.5-canary.2 (#13297) (`e6e6fb6`)
- fix: Stop ambient CARGO_INCREMENTAL from suppressing compile cache
injection (#13298) (`784af75`)
- fix: Never let compile cache storage failures fail the build (#13299)
(`a6fc6c5`)
- test: Add outputs path-traversal negative-case regression tests
(#13300) (`2ff6df9`)
- release(turborepo): 2.10.5-canary.3 (#13302) (`7e44a29`)
- ci: Authenticate to Remote Cache via OIDC token exchange (#13303)
(`a86839c`)
- fix: Keep pnpm patches with version range keys during prune (#13307)
(`3c27a89`)
- test: Scrub ambient turbo configuration from integration test children
(#13306) (`b03d0d6`)
- fix: Show toolchain tasks in the TUI and never run in silence (#13308)
(`d7622b6`)
- ci: Fix change detection on push events (#13304) (`eec3d61`)
- refactor: Rename the Cargo toolchain id to rust (#13311) (`a548b02`)
- fix: Remove extraneous bun.lock entries during prune (#13317)
(`382e9f5`)
- feat: Require a user-declared name for the Cargo workspace package
(#13312) (`5f01746`)
- feat: Parse and validate the task command field (#13313) (`389ea49`)
- chore: Harden turbo-vsc to invoke turbo without a shell (#13319)
(`4a19b6e`)
- feat: Resolve and execute task command overrides (#13315) (`a549baa`)
- release(turborepo): 2.10.5-canary.4 (#13325) (`6626261`)
- feat: Run the Rust workspace tests through nextest via command
override (#13316) (`6711bbc`)
- feat: Engage the Remote Cache and sccache compile cache for Rust CI
(#13292) (`1ae2065`)
- ci: Pin GitHub Actions to full commit SHAs (#13143) (`ad614d6`)
- fix: Make npm prune rehoisting deterministic and complete (#13323)
(`2dac737`)
- feat: Report incremental cache reuse in the run summary (#13327)
(`9f3d24a`)
- chore: Remove planning docs (#13329) (`5cd1d02`)
- docs: Migrate docs site to @vercel/geistdocs package (#13320)
(`5517bb2`)
- ci: Update Remote Cache action (#13330) (`4a39887`)
- release(turborepo): 2.10.5-canary.5 (#13331) (`f699719`)
- ci: Remove path-based workflow scheduling (#13332) (`84f2b2c`)
- fix: Resolve EADDRINUSE in kitchen-sink api dev script (#13328)
(`5886c71`)
- ci: Disable telemetry messages in workflows (#13334) (`2218dea`)
- fix: Prevent Cargo run tasks from being cached (#13335) (`68f449b`)
- fix: Isolate Cargo cache by host platform (#13337) (`c71f5cd`)
- fix: Continue TUI text selection beyond viewport (#13338) (`9cbfd90`)
- ci: Dogfood Cargo target restoration (#13336) (`8ad90a7`)
- fix: Require current Cargo lockfiles for caching (#13339) (`3a3d381`)
- fix: Reject unsupported Cargo local packages (#13340) (`708d656`)
- fix: Allow outputs outside package roots (#13342) (`de6a0d3`)
- fix: Resolve Cargo lock dependencies by source (#13343) (`2223a33`)
- docs: Update Cargo workspace support (#13349) (`241ada8`)
- fix: Build Ghostty for baseline CPUs (#13352) (`a2a04cc`)
- fix: Preserve watch rerun semantics for task inputs (#13351)
(`6ed5eab`)
- fix: Isolate Command Overrides From Toolchain Cache I/O (#13354)
(`e76f0b4`)
- release(turborepo): 2.10.5-canary.6 (#13355) (`f82e2c7`)
- fix: Include patched Ghostty crate in Cargo workspace (#13357)
(`b3cd7a1`)
- ci: Shard Rust tests across runners (#13356) (`ef122d9`)
- fix: Hash Cargo build environment inputs (#13348) (`d533266`)
- fix: Synchronize Cargo prune Docker lockfile (#13350) (`ed17249`)
- chore: Update agents app Eve dependency (#13364) (`38aa7d2`)
- fix: Preserve Yarn package extension ranges when pruning (#13363)
(`6ed2fb4`)
- refactor: Add `Toolchain` output availability (#13360) (`82bcfb6`)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
Preview – turborepo-eve-agent c8b8d3e6 Deployed Jul 7, 2026 by vercel[bot]
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.

2 participants