Skip to content

SIGTTOU when test spawns interactive shell #2878

@max-sixty

Description

@max-sixty

Description of the issue

Currently when running tests for https://github.com/max-sixty/worktrunk/ with nextest, the test process gets backgrounded. I've worked with Claude to get good attribution for this, and a reproduction (it wrote the words below, but I stand behind them).


nextest receives SIGTTOU and gets suspended when a test spawns an interactive shell subprocess (zsh -ic or bash -ic). The issue triggers during InputHandler cleanup when the interactive subprocess causes nextest to lose foreground process group status.

Minimal reproduction:

git clone https://github.com/max-sixty/nextest-sigttou-repro
cd nextest-sigttou-repro
cargo nextest run --test minimal

The test simply spawns an interactive shell:

Command::new("zsh")
    .args(["-ic", "echo hello"])
    .output()

What triggers it:

  • zsh -ic "..." (interactive zsh)
  • bash -ic "..." (interactive bash)
  • Any binary that internally spawns an interactive shell

What doesn't trigger it:

  • Non-interactive shells: zsh -c "...", bash -c "..."
  • Simple binaries (echo, cargo, etc.)
  • Tests run with cargo test (no InputHandler)
  • Non-interactive terminals (CI, pipes)

Root cause:

  1. InputHandler modifies terminal settings at startup (disables echo, canonical mode)
  2. Test spawns zsh -ic which starts an interactive shell
  3. The interactive shell takes control of the terminal's foreground process group
  4. When the subprocess exits, nextest is no longer in the foreground process group
  5. During cleanup, InputHandlerImpl::restore calls tcsetattr()
  6. tcsetattr() on a background process triggers SIGTTOU
  7. Process is suspended

The foreground check at startup (added in 0.9.94) doesn't prevent this because the process group status changes during execution.

Suggested fix: Block SIGTTOU during tcsetattr() in InputHandler cleanup (common pattern used by less, vim, etc.)

Expected outcome

Tests complete normally without nextest being suspended.

Actual result

zsh: suspended (tty output)  cargo nextest run --test minimal

When resumed with fg:

failed to restore terminal state: failed to restore terminal state

Nextest version

cargo-nextest 0.9.114 (ff4ee9ac9 2025-11-19)
release: 0.9.114
commit-hash: ff4ee9ac9e7feb312e2ba6ea2d5eec40142a2ce0
commit-date: 2025-11-19
host: aarch64-apple-darwin

Additional context

Workaround: NEXTEST_NO_INPUT_HANDLER=1 cargo nextest run

Environment: macOS 14.x, zsh terminal (also affects Linux)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions