Reduce fallback Unix shell activation latency without changing native startup behavior
- Dominant language
- Python
- Stars
- 6
- Forks
- 4
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 13
Description
After [#22](https://github.com/conda/conda-spawn/pull/22), `UnixShell.spawn_tty()` waits until the activation script prints a readiness marker before handing the terminal to the user. That wait is required for correctness. Before #22, `conda spawn` could proceed after reading only stale pre-activation output and let user input race with activation.
The remaining work is to make the fallback path reliable and to measure and reduce startup latency for Unix shells whose activation script is sent through the PTY after the shell process starts.
### Historical profile
The original profile used conda-spawn 0.1.0, Python 3.12, conda-forge, Bash, and macOS arm64. The numbers below are medians from five runs on that setup:
| Step | Time |
| --- | --- |
| Build the activation script | ~1 ms |
| `pexpect.spawn(bash, -l, -i)` returns | ~2.7 s |
| Send the source command | ~55 ms |
| Wait for the readiness marker | ~3.1 s |
| Total | ~6.0 s |
These measurements establish the total latency on that machine, but they do not establish a second ~2.5-second PTY allocation or an inherent PTY round-trip cost. There is one PTY and one shell process. The marker wait can include shell startup still running in the child, native startup files, prompt hooks, terminal input handling, activation, and marker observation.
The delay became visible when [conda-workspaces#49](https://github.com/conda-incubator/conda-workspaces/pull/49) re-recorded its shell demo against conda-spawn 0.1.0.
### Current profile
A 2026-08-31 profile of [`main` at `1a943c6`](https://github.com/conda/conda-spawn/commit/1a943c6bea8c9b49fbd09179e768de4b619eef3b) used Python 3.12.8, conda 24.11.3, pexpect 4.9.0, Bash 5.3.15, conda-forge, and macOS arm64. The numbers below are medians from five runs:
| Step | Bash |
| --- | --- |
| Build the activation script | ~0.7 ms |
| `pexpect.spawn(bash, -l, -i)` returns | ~106 ms |
| Send the source command | ~55 ms |
| Wait for the readiness marker | ~3 ms |
| Total | ~166 ms |
The first run took ~410 ms because activation-script generation was cold. The same profile measured Homebrew Zsh 5.9.2 at ~1.63 s total, with ~1.47 s spent between sending the source command and observing the marker. The historical six-second Bash result therefore does not describe current `main`, but fallback-shell latency remains configuration-dependent.
### Current status
Merged [#36](https://github.com/conda/conda-spawn/pull/36) injects activation through documented startup options where that preserves user behavior:
- Fish uses `-C`.
- Xonsh uses `--rc` and explicitly preserves configured `XONSHRC`, `XONSHRC_DIR`, `on_post_rc`, and `on_pre_cmdloop` behavior.
The same work found that the proposed hooks for other shells changed native startup behavior:
- Bash `--rcfile -i` creates a non-login shell. Manually sourcing login files does not restore Bash's login state, native startup-file selection, or `.bash_logout` behavior.
- Zsh `ZDOTDIR` changes startup-file discovery and can disturb an existing `ZDOTDIR`, `RCS`, nested shells, and logout behavior.
- POSIX `ENV` can be replaced or unset by `.profile` and can leak activation into nested shells.
POSIX shells, Bash, Zsh, csh, and tcsh therefore retain their native startup followed by the PTY source command and readiness marker.
### Homebrew Zsh startup failure
The current fallback path also exposed a correctness failure. With Homebrew Zsh 5.9.2 and an empty home directory on macOS arm64, the initial PTY source command was not executed in ten of ten runs. Sending the identical command after the first prompt succeeded in three of three runs. System `/bin/zsh` 5.9 succeeded in three of three runs.
This shows that the fallback command can be lost during startup in at least one supported shell configuration. Conda-spawn still waits for the marker and fails instead of handing an unactivated shell to the user, but the fallback path must avoid losing the source command.
### Comparison with Pixi
For Bash, Zsh, Fish, and Xonsh on Unix, Pixi starts one shell in one PTY, sends a source command, and suppresses shell output until it sees `PIXI_SHELL_ACTIVATION_DONE`. Its interaction loop forwards user input immediately rather than gating it on the marker. If the marker is still absent after three seconds, Pixi warns, prints the retained buffered output, and forwards subsequent output. Pixi's Unix Bash and Zsh shells are non-login, and Pixi does not provide equivalent POSIX, csh, or tcsh paths. Its Windows Git Bash path uses `--init-file`.
See Pixi's [Unix shell setup](https://github.com/prefix-dev/pixi/blob/bab987121de8c202146a9c7f532966eabd4f01ec/crates/pixi_cli/src/shell.rs#L211-L275), [PTY interaction loop](https://github.com/prefix-dev/pixi/blob/bab987121de8c202146a9c7f532966eabd4f01ec/crates/pixi_pty/src/unix/pty_session.rs#L115-L251), and [Windows Git Bash setup](https://github.com/prefix-dev/pixi/blob/bab987121de8c202146a9c7f532966eabd4f01ec/crates/pixi_cli/src/shell.rs#L145-L203).
Conda-spawn should not copy Pixi's input behavior or continue after a readiness timeout because either choice can hand control to the user before activation finishes and regress #22. A timeout may produce a clear failure, but it must not treat the shell as ready.
### Requirements for further work
- Preserve each shell's native startup-file order, login state, user functions and options, prompt hooks, nested-shell behavior, and logout behavior.
- Complete activation before user input is accepted.
- Keep the unique readiness marker after activation.
- Ensure the post-start source command cannot be lost while the shell initializes.
- Keep a reproducible profile that separates activation-script generation, process spawn, post-start send, native and user startup, activation, and marker observation.
- Investigate safe optimizations for POSIX shells, Bash, Zsh, csh, and tcsh.
### Workaround
The remaining wait is intentional for callers that drive `conda spawn`. Demos and screencasts can reduce explicit sleep durations in their recording scripts, as [conda-workspaces#49](https://github.com/conda-incubator/conda-workspaces/pull/49) did.
Contributor guide
Assessment
This issue has not been assessed yet.