Skip to content

feat(task): add interactive field for exclusive terminal access#8491

Merged
jdx merged 11 commits intomainfrom
feat/interactive-task-lock
Mar 7, 2026
Merged

feat(task): add interactive field for exclusive terminal access#8491
jdx merged 11 commits intomainfrom
feat/interactive-task-lock

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 7, 2026

Summary

  • Adds interactive = true task field that acquires an exclusive write lock on a global RwLock, giving the task sole access to stdin/stdout/stderr
  • Non-interactive tasks use a shared read lock and continue running in parallel with each other
  • More targeted alternative to raw = true which forces jobs=1 globally — interactive only blocks concurrent tasks while the interactive task is running
  • Supersedes feat(tasks): minimal interactive runtime rework with segment rwlock #8473 with a much simpler approach: no cmd.rs changes, no TTY save/restore, no setpgid

Changes

  • src/task/mod.rs — add interactive: bool field, file-task parsing, default
  • src/task/task_executor.rs — global TASK_RUNTIME_LOCK: RwLock<()>, acquire write (interactive) or read (non-interactive) before cmd.execute()
  • src/task/task_output_handler.rs — treat interactive as raw for I/O
  • src/cli/tasks/info.rs, src/cli/tasks/ls.rs, src/cli/mcp.rs — add interactive to JSON output
  • schema/mise-task.json, schema/mise.json — add interactive boolean property
  • e2e/tasks/test_task_info, e2e/tasks/test_task_ls — update expectations

Test plan

  • mise run lint-fix passes
  • cargo test — all 520 unit tests pass
  • mise run test:e2e test_task_info test_task_ls passes
  • Manual test: interactive-task ::: bg-task-1 ::: bg-task-2 — interactive task waits for exclusive access while bg tasks run in parallel

🤖 Generated with Claude Code


Note

Medium Risk
Touches core task execution/concurrency and terminal I/O behavior via a new global lock, which could introduce deadlocks or unexpected scheduling/throughput changes if edge cases aren’t covered.

Overview
Adds a new boolean task field interactive (default false) that marks a task as requiring exclusive terminal access, and wires it through task parsing and defaults.

Task execution now uses a global runtime RwLock to coordinate concurrency: interactive tasks take an exclusive lock (including during confirmation prompts), while non-interactive tasks take a shared lock; the lock is held across consecutive run-script entries but dropped while waiting on injected sub-tasks to avoid deadlocks, and command execution is wrapped in block_in_place to avoid starving the tokio runtime.

CLI/MCP JSON outputs and tasks info property display now include interactive, JSON schemas (mise.json, mise-task.json) are updated accordingly, interactive tasks are treated as raw I/O in the output handler (with a new hint warning about redactions), and e2e expectations are updated.

Written by Cursor Bugbot for commit 4126daa. This will update automatically on new commits. Configure here.

Tasks marked `interactive = true` acquire an exclusive write lock,
ensuring they get sole access to stdin/stdout/stderr. Non-interactive
tasks use a shared read lock and can still run in parallel with each
other. This is a more targeted alternative to `raw = true` which
forces `jobs=1` globally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new interactive field for tasks within mise, providing a more refined way to manage terminal access. Instead of the broad raw=true setting that forces single-threaded execution, interactive=true tasks now acquire an exclusive write lock on a global RwLock, ensuring sole access to standard I/O. Concurrently, non-interactive tasks can proceed in parallel by acquiring a shared read lock, significantly enhancing task concurrency and user experience for interactive processes without sacrificing system stability.

Highlights

  • New interactive task field: Introduced a boolean interactive field for tasks, defaulting to false, to control exclusive terminal access.
  • Exclusive Terminal Access: Implemented a global RwLock (TASK_RUNTIME_LOCK) to manage terminal I/O, where interactive tasks acquire an exclusive write lock and non-interactive tasks acquire a shared read lock.
  • Improved Concurrency: This new mechanism allows non-interactive tasks to run in parallel while an interactive task holds exclusive terminal access, offering a more targeted alternative to the global raw=true setting.
  • Schema and CLI Updates: Updated mise-task.json and mise.json schemas, and CLI commands (mcp, tasks info, tasks ls) to include and display the new interactive field.
  • I/O Handling: Interactive tasks are now treated as raw for I/O purposes, ensuring direct connection to stdin/stdout/stderr.
Changelog
  • e2e/tasks/test_task_info
    • Updated expected JSON output to include the interactive field.
  • e2e/tasks/test_task_ls
    • Updated expected JSON output for various task listings to include the interactive field.
  • schema/mise-task.json
    • Added the interactive boolean property with a default value and description.
  • schema/mise.json
    • Added the interactive boolean property to task definitions within the main schema.
  • src/cli/mcp.rs
    • Modified the ServerHandler implementation to include the interactive task field in JSON responses.
  • src/cli/tasks/info.rs
    • Updated the TasksInfo implementation to include the interactive task field in JSON output.
  • src/cli/tasks/ls.rs
    • Updated the TasksLs implementation to include the interactive task field in JSON output.
  • src/task/mod.rs
    • Added the interactive boolean field to the Task struct.
    • Updated parsing logic to read this field.
    • Set its default value.
    • Included it in a test example.
  • src/task/task_executor.rs
    • Introduced a LazyLock<RwLock<()>> named TASK_RUNTIME_LOCK for managing concurrent task execution.
    • Modified the execute method to acquire a write lock for interactive tasks and a read lock for non-interactive tasks before command execution.
  • src/task/task_output_handler.rs
    • Updated the raw method to consider tasks with interactive: true as raw for output handling.
Activity
  • All unit tests (cargo test) passed.
  • End-to-end tests for test_task_info and test_task_ls passed.
  • mise run lint-fix passed.
  • Manual testing confirmed that interactive tasks correctly acquire exclusive access while background tasks run in parallel.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an interactive field for tasks, providing a mechanism for exclusive terminal access. This is achieved using a global RwLock in task_executor.rs, where interactive tasks acquire a write lock and non-interactive tasks acquire a read lock. This is a well-designed alternative to using raw = true which would limit concurrency globally. The changes are consistently applied across schema definitions, CLI outputs, and task handling logic. The logic for treating interactive tasks as raw for I/O purposes in task_output_handler.rs is also correct. The implementation is clean and effective. The e2e tests have been updated accordingly. Overall, this is a great addition.

- Move lock acquisition from exec_program to exec_task_run_entries and
  exec_file callers, so the lock is held across the full task execution
  (not released between multi-step run entries)
- Skip misleading "--raw will prevent redactions" hint for interactive
  tasks since the user set interactive=true, not raw=true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR adds an interactive = true task field that grants a task exclusive terminal access (via a global tokio::sync::RwLock write lock) while still allowing other non-interactive tasks to run concurrently under a shared read lock — a more targeted alternative to raw = true, which forces jobs=1 globally.

Key changes:

  • src/task/task_executor.rs — introduces TASK_RUNTIME_LOCK: LazyLock<RwLock<()>> and a RuntimeLockGuard enum; interactive tasks acquire the write lock (before confirmation prompts), non-interactive tasks acquire the read lock (before execution); wraps the blocking cmd.execute() call in tokio::task::block_in_place to avoid Tokio runtime starvation while the lock is held
  • src/task/task_output_handler.rsraw() method now includes t.interactive, so interactive tasks inherit stdin/stdout/stderr directly; critically, jobs() still uses the raw struct field (not the method), so interactive does not implicitly force jobs=1
  • src/task/mod.rs — adds interactive: bool field, file-task header parsing, default value, and a unit test
  • CLI (mcp.rs, tasks/info.rs, tasks/ls.rs) and JSON schemas (mise.json, mise-task.json) — expose interactive in all relevant outputs and validators; e2e expectations updated accordingly

Issues found:

  • In exec_task_run_entries, non-interactive tasks acquire the read lock at the top of the function (before any script runs) and hold it across the entire consecutive-script chain, rather than lazily per-script. This makes interactive tasks wait longer than necessary when a non-interactive task has many consecutive inline scripts.
  • The _guard in exec_file is wrapped in an unnecessary Option for non-interactive tasks; a direct match would be cleaner and clarify that the lock is always held before exec().
  • The interactive_redactions hint is suppressed when task.interactive = true AND task.raw = true simultaneously — the user only sees the generic raw_redactions message, potentially missing the information that the interactive flag itself is bypassing redactions.

Confidence Score: 3/5

  • Functional but has a lock-timing inconsistency that could make interactive tasks wait longer than expected, and a hint condition that silently swallows the redactions warning for tasks with both interactive and raw set.
  • The core locking mechanism is logically correct — no deadlocks identified, block_in_place is used properly, and the RwLock correctly serializes interactive vs. non-interactive tasks. However, exec_task_run_entries acquires the read lock for non-interactive tasks before any script actually runs (inconsistent with exec_file which acquires it just before exec()), causing potentially longer-than-expected waits for interactive tasks. The redactions hint condition also silently omits the interactive warning when raw=true is also set. These are not crash-level bugs but affect correctness of user-facing behavior.
  • src/task/task_executor.rs — lock acquisition timing in exec_task_run_entries and hint condition in exec_program both need attention

Important Files Changed

Filename Overview
src/task/task_executor.rs Core change: introduces TASK_RUNTIME_LOCK (tokio RwLock), acquire_runtime_lock helper, and RuntimeLockGuard enum; acquires write lock for interactive tasks and read lock for non-interactive tasks before exec_program/exec_file/exec_script; adds block_in_place around cmd.execute(); updates redaction hint logic. Three issues found: read lock acquired prematurely in exec_task_run_entries (before scripts actually run), redundant Option wrapping of _guard in exec_file, and interactive_redactions hint suppressed when raw=true is also set.
src/task/task_output_handler.rs raw() method now returns true for interactive tasks (t.raw
src/task/mod.rs Adds interactive: bool field with #[serde(default)], parses it from file-task headers via parse_bool("interactive"), adds it to Default::default(), and adds a test case in the file-task parsing unit test. All changes are correct and consistent.
src/cli/tasks/info.rs Adds interactive to the human-readable Properties section (shown only when true) and to the --json output. Correct and minimal change.
src/cli/tasks/ls.rs Adds interactive field to --json output in tasks ls. Correct and minimal change.
src/cli/mcp.rs Adds task.interactive to the MCP task listing JSON. Correct and minimal change.
schema/mise-task.json Adds interactive boolean property (default: false) in two schema locations. Consistent with the implementation.
schema/mise.json Adds interactive boolean property (default: false) in three schema locations. Consistent with the implementation.
e2e/tasks/test_task_info Updates expected JSON output to include "interactive": false. Correct.
e2e/tasks/test_task_ls Updates six expected JSON objects to include "interactive": false. Correct.

Sequence Diagram

sequenceDiagram
    participant Sched as Task Scheduler
    participant ExecTask as exec_task / exec_file
    participant Lock as TASK_RUNTIME_LOCK
    participant Cmd as CmdLineRunner (block_in_place)

    Note over Sched: Interactive task + 2 non-interactive tasks dispatched

    Sched->>ExecTask: interactive-task (interactive=true)
    ExecTask->>Lock: write().await (exclusive)
    Lock-->>ExecTask: WriteGuard held

    par non-interactive tasks try to start
        Sched->>Lock: read().await (bg-task-1)
        Note over Lock: Blocked — writer held
        Sched->>Lock: read().await (bg-task-2)
        Note over Lock: Blocked — writer held
    end

    ExecTask->>Cmd: block_in_place(cmd.execute())
    Cmd-->>ExecTask: process exits
    ExecTask->>Lock: drop WriteGuard

    par bg tasks now unblocked
        Lock-->>Sched: ReadGuard (bg-task-1)
        Lock-->>Sched: ReadGuard (bg-task-2)
        Note over Sched: bg-task-1 and bg-task-2 run in parallel
    end
Loading

Comments Outside Diff (3)

  1. src/task/task_executor.rs, line 329-332 (link)

    Read lock acquired before scripts run, delaying interactive tasks

    For non-interactive tasks (existing_guard = None, task.interactive = false), the read lock is acquired here — at the very start of exec_task_run_entries — before any script has actually started executing. If the task's first (and possibly only) entries are all RunEntry::SingleTask / RunEntry::TaskGroup, the read lock is acquired and then immediately set to None on the first loop iteration, creating a spurious window where the read lock is briefly held with no real work being done.

    More importantly, because the read lock is held across the entire consecutive-script sequence (not just per-script), an interactive task waiting for the write lock is blocked for the whole chain. By contrast, on the exec_file path for non-interactive tasks, the read lock is only acquired immediately before exec() and released as soon as exec() returns. This inconsistency means that a non-interactive task with many consecutive inline scripts blocks an interactive task much longer than a non-interactive file task doing the same amount of work.

    Consider acquiring the lock lazily (only when a RunEntry::Script is actually about to execute) and always releasing it between distinct script entries, rather than holding it from the top of the function:

    // Instead of acquiring upfront, start with no guard and acquire per-script:
    let mut guard: Option<RuntimeLockGuard<'static>> = match existing_guard {
        Some(g) => Some(g),  // interactive: keep the write-guard from confirm
        None => None,        // non-interactive: acquire lazily before each script
    };
  2. src/task/task_executor.rs, line 635-639 (link)

    Redundant is_some check — simplify with or_else

    guard is either Some(write_lock) (interactive) or None (non-interactive). The if guard.is_some() { guard } else { … } pattern is equivalent to a simple or_else / unwrap_or_else:

    This also avoids wrapping the result in a redundant Option since the lock will always be acquired by this point — holding it in an Option obscures intent and won't trigger the must_use lint on RwLockWriteGuard.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  3. src/task/task_executor.rs, line 704-718 (link)

    Interactive redactions hint silenced when raw = true is also set

    The hint branch condition task.interactive && !task.raw && !Settings::get().raw means that when a task has both interactive = true and raw = true, the condition is false and the user receives only the generic raw_redactions hint — with no mention that the interactive flag is also bypassing redactions. A user who later removes raw = true (thinking the hint only applied to raw) would silently lose the warning about redactions being bypassed.

    Consider checking task.interactive independently of whether raw was also set:

Fix All in Claude Code

Last reviewed commit: 4126daa

jdx and others added 3 commits March 7, 2026 02:43
…o text output

- Wrap cmd.execute() with tokio::task::block_in_place since it blocks
  the thread while holding a tokio RwLock guard
- Add "interactive" to human-readable `mise task info` properties
- Add comment explaining intentional double-lock with RAW_LOCK

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ck acquisition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…usivity

Acquire the lock once before the run-entry loop so consecutive script
entries maintain terminal exclusivity for interactive tasks. The lock
is temporarily dropped around inject_and_wait calls to avoid deadlocking
with sub-tasks that also need to acquire the lock.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move lock acquisition from the caller into exec_file, after
parse_usage_spec_and_init_env and check_confirmation complete.
This avoids blocking all tasks during the confirmation dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jdx and others added 2 commits March 7, 2026 03:15
…ile tasks

For interactive file-based tasks, acquire the lock before the
confirmation prompt so it gets exclusive terminal access and doesn't
race with concurrent task output. Non-interactive tasks still acquire
the lock after confirmation to avoid blocking the task graph.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Interactive tasks bypass redactions since they use raw I/O. Instead of
silently suppressing the warning, emit a specific hint so users are
aware secrets may appear in terminal output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tasks

Consistent with the exec_file path, acquire the runtime lock before
check_confirmation for interactive run-entry tasks so the confirmation
prompt gets exclusive terminal access. The lock is dropped after
confirmation and re-acquired inside exec_task_run_entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

jdx and others added 2 commits March 7, 2026 03:35
Instead of dropping the confirmation guard and re-acquiring a new one
(leaving a gap where other tasks could print), pass the existing guard
into exec_task_run_entries so exclusivity is maintained seamlessly from
confirmation through script execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…clippy warning

Show raw_redactions hint when task has explicit raw config, even if also
interactive. Only show interactive_redactions when raw is solely due to
interactive mode. Suppress too_many_arguments clippy lint on
exec_task_run_entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx enabled auto-merge (squash) March 7, 2026 03:53
@jdx jdx merged commit c7382f7 into main Mar 7, 2026
36 checks passed
@jdx jdx deleted the feat/interactive-task-lock branch March 7, 2026 03:53
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 7, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 x -- echo 23.8 ± 0.7 22.7 26.0 1.02 ± 0.04
mise x -- echo 23.4 ± 0.5 22.7 28.6 1.00

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 env 23.0 ± 0.7 22.2 29.1 1.02 ± 0.04
mise env 22.6 ± 0.6 22.0 32.9 1.00

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 hook-env 23.6 ± 0.3 23.0 27.8 1.01 ± 0.02
mise hook-env 23.4 ± 0.3 22.7 24.8 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.3 ls 23.1 ± 0.5 22.4 27.3 1.01 ± 0.03
mise ls 22.9 ± 0.4 22.1 24.5 1.00

xtasks/test/perf

Command mise-2026.3.3 mise Variance
install (cached) 152ms 151ms +0%
ls (cached) 84ms 82ms +2%
bin-paths (cached) 85ms 84ms +1%
task-ls (cached) 836ms 811ms +3%

mise-en-dev added a commit that referenced this pull request Mar 7, 2026
### 🚀 Features

- **(github)** keep exe extensions on Windows by @iki in
[#8424](#8424)
- **(task)** add `interactive` field for exclusive terminal access by
@jdx in [#8491](#8491)
- add header comment to generated lockfiles by @ivy in
[#8481](#8481)
- runtime musl/glibc detection for correct libc variant selection by
@jdx in [#8490](#8490)

### 🐛 Bug Fixes

- **(github)** use registry platform options during install by @jdx in
[#8492](#8492)
- **(http)** store tool opts as native TOML to fix platform switching by
@jdx in [#8448](#8448)
- **(installer)** error if MISE_INSTALL_PATH is a directory by @jdx in
[#8468](#8468)
- **(prepare)** resolve sources/outputs relative to `dir` when set by
@jdx in [#8472](#8472)
- **(ruby)** fetch precompiled binary by release tag instead of listing
all releases by @jdx in [#8488](#8488)
- **(schema)** support structured objects in task depends by @risu729 in
[#8463](#8463)
- **(task)** replace println!/eprintln! with calm_io in task output
macros by @vmaleze in [#8485](#8485)
- handle scoped npm package names without backend prefix by @jdx in
[#8477](#8477)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:copr docker digest to c485c4c by
@renovate[bot] in [#8484](#8484)
- update ghcr.io/jdx/mise:alpine docker digest to 8118bc7 by
@renovate[bot] in [#8483](#8483)

### 📦 Registry

- disable sd version test by @jdx in
[#8489](#8489)

### New Contributors

- @ivy made their first contribution in
[#8481](#8481)
- @iki made their first contribution in
[#8424](#8424)

## 📦 Aqua Registry Updates

#### New Packages (5)

- [`datadog-labs/pup`](https://github.com/datadog-labs/pup)
- [`k1LoW/mo`](https://github.com/k1LoW/mo)
- [`rtk-ai/rtk`](https://github.com/rtk-ai/rtk)
-
[`suzuki-shunsuke/docfresh`](https://github.com/suzuki-shunsuke/docfresh)
- [`yashikota/exiftool-go`](https://github.com/yashikota/exiftool-go)

#### Updated Packages (6)

- [`cloudflare/cloudflared`](https://github.com/cloudflare/cloudflared)
- [`mozilla/sccache`](https://github.com/mozilla/sccache)
- [`owenlamont/ryl`](https://github.com/owenlamont/ryl)
- [`spinel-coop/rv`](https://github.com/spinel-coop/rv)
-
[`technicalpickles/envsense`](https://github.com/technicalpickles/envsense)
- [`weaviate/weaviate`](https://github.com/weaviate/weaviate)
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