aws / aws/amazon-q-developer-cli
bug: Amazon Q CLI Significantly degrades ZSH performance
- Dominant language
- Rust
- Stars
- 2k
- Forks
- 439
- PR merge metrics
- No merged PRs in 30d
Description
### Checks
- [x] I have searched [github.com/aws/amazon-q-developer-cli/issues](https://github.com/aws/amazon-q-developer-cli/issues?q=) and there are no duplicates of my issue
- [x] I have run `q doctor` in the affected terminal session
- [x] I have run `q restart` and replicated the issue again
### Operating system
macOS 15.3.2
### Expected behaviour
Amazon Q CLI not to slow down terminal initialisation or impact runtime performance.
### Actual behaviour
# Q CLI Analysis
The Amazon Q CLI significantly slows down the loading of new terminal sessions on macOS when integrated with `zsh`. Profiling and analysis reveal that the primary causes are double initialisation and a slow pre-command hook.
**Environment:**
- **Shell:** `zsh`
- **Operating System:** macOS 15.3.2
- **Amazon Q CLI Installation Method:** The latest official Amazon Q installer
**Problem Summary:**
After Amazon Q CLI makes changes to add itself into both `~/.zshrc` and `~/.zprofile`, the startup time of new terminal sessions increased dramatically. Furthermore, the shell felt generally sluggish, with a noticeable delay before each prompt appeared.
**Detailed Findings:**
1. **Double Initialisation:** The Amazon Q CLI initialisation script (`q init zsh pre`) is executed *twice* during shell startup: once from `~/.zprofile` and again from `~/.zshrc`. The scripts generated for `pre` are identical for both files. This leads to redundant operations and significantly increases the initial load time.
- **Evidence:** `zsh` profiling (`zprof`) and tracing (`set -x`) clearly show the repeated execution of the initialisation logic.
2. **Slow `q _ pre-cmd`:** The `q _ pre-cmd` command, which is executed *before every prompt* via a `precmd` hook, introduces a significant delay. This delay is the primary cause of the perceived sluggishness of the shell after the initial load.
- **Evidence:** Timing measurements using the `time` command show that `q _ pre-cmd` takes approximately 11ms to complete:
```shell
time $HOME/.local/bin/q _ pre-cmd --alias "$(\alias)"
```
Output:
```shell
$HOME/.local/bin/q _ pre-cmd --alias "$(\alias)" 0.00s user 0.00s system 85% cpu 0.011 total
```
This 11ms delay is added to *every* command execution, making the shell feel unresponsive.
3. **`q _ should-figterm-launch` Contributes to Startup Delay:** The `q _ should-figterm-launch` command, executed during initialisation, also contributes to the initial startup delay.
- **Evidence:** Timing measurements:
```shell
time $HOME/.local/bin/q _ should-figterm-launch
```
Output:
```shell
❌ zsh (qterm) | (46360) <- ✅ zsh (46368)
🟡 Falling back to old mechanism since on macOS
$HOME/.local/bin/q _ should-figterm-launch 0.01s user 0.01s system 93% cpu 0.013 total
```
This adds 13ms to the startup time.
4. **`qterm` is not being launched:** The output from `q _ should-figterm-launch` indicates it is falling back to an "old mechanism".
**Steps to Reproduce:**
1. Profile zsh without Q
2. Install the Amazon Q CLI.
3. Integrate the CLI into `zsh` by adding the recommended lines to `~/.zshrc` and `~/.zprofile`.
4. Profile zsh with Q
5. Open a new terminal session and observe the slow startup time.
6. Type commands and observe the delay before each prompt.
---
## Proposed Solutions/Workarounds
1. **Prevent Double Initialisation:** Modified `.zprofile` to include a guard variable, preventing the initialisation block from running in `.zshrc`:
```zsh
# In ~/.zprofile:
if [[ -z "$AMAZON_Q_INITIALIZED" ]]; then
if [[ -f '$HOME/Library/Application Support/amazon-q/shell/zprofile.pre.zsh' ]]; then
source '$HOME/Library/Application Support/amazon-q/shell/zprofile.pre.zsh'
fi
if [[ -f '$HOME/Library/Application Support/amazon-q/shell/zprofile.post.zsh' ]]; then
source '$HOME/Library/Application Support/amazon-q/shell/zprofile.post.zsh'
fi
export AMAZON_Q_INITIALIZED=1
fi
```
Removed the entire Amazon Q block from `~/.zshrc`. This significantly reduced the *initial* startup time.
2. **Disable `q _ pre-cmd`:** Commented out the call to `q _ pre-cmd` within the `fig_precmd` function and also the call outside the function definition, in `$HOME/Library/Application Support/amazon-q/shell/zprofile.post.zsh`. This *dramatically* improved the responsiveness of the shell after startup, eliminating the per-prompt delay.
```zsh
fig_precmd() {
# ... (other code) ...
# if command -v q >/dev/null 2>&1; then
# (command q _ pre-cmd --alias "$(\alias)" > /dev/null 2>&1 &) >/dev/null 2>&1
# fi
}
fig_reset_hooks
if [[ -n "${PROCESS_LAUNCHED_BY_Q:-}" ]]; then
fig_osc DoneSourcing
fi
# fi # <- this was already commented out
#(command q _ pre-cmd --alias "$(\alias)" > /dev/null 2>&1 &) >/dev/null 2>&1 # comment this out.
```
---
## Quantitative Performance Impact
The following table summarizes the relevant key differences in execution times of `zsh` functions during startup, as measured by `zprof`, with and without Amazon Q CLI integration.
Note: These timings only reflect the *initial* shell startup, and *do not* include the ongoing overhead of the `precmd` hook (which adds ~11ms to *every* command, as detailed separately).
| Function | Without Q (ms) | With Q (ms) | Difference (ms) | Notes |
|:-------------------------------|---------------:|------------:|----------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Amazon Q Initialisation** | **0** | **~1860** | **+~1860** | This is a *significant* difference. This represents the time spent in the Amazon Q initialisation blocks (running `q init` and `eval`ing the result), derived from earlier `set -x` tracing. |
| **TOTAL (Approximate)** | **~125** | **~1940** | **+~1815** | Total is estimated based on zprof times and tracing, showing a difference close to the 2 seconds noted in the earlier `set -x` tracing. This highlights the massive impact of the Amazon Q initialisation. |
## Key Observations
- **Overall Startup Increase:** The total startup time increases by approximately 1.8 seconds with the Amazon Q CLI enabled, *before* considering the `precmd` hook overhead (~11ms per command).
- **Amazon Q Initialisation Dominates:** The vast majority of the increased startup time is directly attributable to the Amazon Q initialisation process. The other functions are comparatively minor.
This table clearly demonstrates the significant negative impact of the Amazon Q CLI on shell startup time, providing quantitative evidence to support the qualitative observation of a slower terminal. The dominant factor is the Amazon Q initialisation itself.
---
## Methodology
I performed analysis of the scripts that Amazon Q CLI injects into the shell, and used zsh's profiling (`zprof`) and tracing (`set -x`) to identify the key performance bottlenecks.
The scripts are generated from the following four commands that I exported to files for analysis:
```bash
${HOME}/.local/bin/q init zsh pre --rcfile zprofile > q_init_pre_zprofile.zsh
${HOME}/.local/bin/q init zsh pre --rcfile zshrc > q_init_pre_zshrc.zsh
${HOME}/.local/bin/q init zsh post --rcfile zprofile > q_init_post_zprofile.zsh
${HOME}/.local/bin/q init zsh post --rcfile zshrc > q_init_post_zshrc.zsh
```
## Detailed Q Scripts Analysis
- **`q_init_pre_zprofile.zsh` and `q_init_pre_zshrc.zsh` (Identical):** These two files are *identical*, which immediately confirms the double-initialisation problem. This is the primary cause of the initial slowdown. The script does the following:
- **Sets up `PATH`:** Adds `~/.local/bin` to the `PATH` if it exists and isn't already there. This is generally good practice, but not a major performance concern.
- **`Q_NEW_SESSION` Handling:** Unsets some `QTERM` related variables if `Q_NEW_SESSION` is set. This suggests some session management, but it's unlikely to be a significant slowdown on its own.
- **`Q_SET_PARENT_CHECK` Handling:** Deals with parent process IDs. Again, not likely to be a major performance bottleneck.
- **`SHOULD_QTERM_LAUNCH` Check:** This is a *crucial* part. It calls `q _ should-figterm-launch` and stores the exit code. This is a likely source of slowdown, as it's an external call to the `q` command. We'll need to investigate `q _ should-figterm-launch`.
- **The `qterm` Launch Logic:** The large `if` block checks several conditions before potentially launching `qterm`:
- `[[ -t 1 ]]`: Checks if standard output is connected to a terminal (i.e., it's an interactive shell).
- `[[ -z "${PROCESS_LAUNCHED_BY_Q:-}" ]]`: Checks if the process was launched by Q. This prevents nested invocations.
- `command -v qterm 1>/dev/null 2>&1`: Checks if the `qterm` command exists. This is a *repeated* check (it also happens inside `q _ should-figterm-launch`, very likely).
- The complex `SHOULD_QTERM_LAUNCH` and `Q_TERM`, `Q_TERM_TMUX`, `TMUX` checks.
- If all conditions are met, it determines the `Q_TERM_NAME` and `Q_TERM_PATH`, and then uses `exec -a "${Q_TERM_NAME}" "${Q_TERM_PATH}"` to *replace* the current shell process with `qterm`. The `-a` option sets the name that `qterm` sees as its own process name (argv[0]).
- **Reading Initial Text:** The `while read -rt 0` loop attempts to read initial text from standard input. This is likely related to how `qterm` interacts with the terminal, but it's only executed if `qterm` is launched. The timeout (`-t 0`) means it won't block, but it's still a bit of extra work.
- **`q_init_post_zprofile.zsh` and `q_init_post_zshrc.zsh` (Identical):** These files are also identical, and they are responsible for setting up the `preexec` and `precmd` hooks.
- **`fig_osc` Function:** Defines a function to send OSC (Operating System Command) escape sequences to the terminal. This is used for communication between Q and the terminal emulator. The function itself is fast.
- **`fig_preexec` and `fig_precmd`:** These are the hook functions. They are called *before every command* (`preexec`) and *before every prompt* (`precmd`).
- **Prompt Restoration (in `fig_preexec`):** Restores the user's original prompt settings. This is important because Q modifies the prompt.
- **OSC Messages (in both):** Sends a series of `fig_osc` calls to communicate with `qterm`. This includes sending the current directory, shell, PID, exit code, etc. This is the *ongoing* overhead that will affect every command.
- **Prompt Modification (in `fig_precmd`):** This is the core of how Q integrates. It saves the user's prompt settings (PS1, PS2, etc.) and then *wraps* them with `START_PROMPT` and `END_PROMPT` markers. These markers are OSC escape sequences (`\033]697;...`). This allows `qterm` to identify the prompt and inject its own output.
- **`q _ pre-cmd` Call:** At the end of `fig_precmd`, it calls `(command q _ pre-cmd --alias "$(\alias)" > /dev/null 2>&1 &) >/dev/null 2>&1`. This runs `q _ pre-cmd` in the background *before every prompt*. This is another likely source of *ongoing* slowdown, and we need to investigate what `q _ pre-cmd` does. The `--alias "$(\alias)"` part is passing a list of defined aliases to the command.
## Key Findings and Confirmed Problems
1. **Double Initialisation (Confirmed):** The `pre` scripts are identical and run twice, causing unnecessary work and likely doubling the startup time.
2. **`q _ should-figterm-launch` (Likely Slowdown):** This external call is made in the `pre` script and is a prime suspect.
3. **`qterm` Launch (Potential Slowdown):** The conditions for launching `qterm` are complex, and `qterm` itself is an unknown quantity.
4. **`preexec`/`precmd` Hooks (Ongoing Overhead):** These hooks add overhead to *every* command and prompt, making the terminal feel sluggish. The `fig_osc` calls and the `q _ pre-cmd` call are the main contributors to this overhead.
5. **`q _ pre-cmd` called before every prompt:** (Likely Slowdown).
---
## Requests for Amazon Q CLI Developers
1. **Address Double Initialisation:** Modify the installation/integration process to prevent the initialisation script from running multiple times. A simple check for an environment variable (like the `AMAZON_Q_INITIALIZED` example above) would suffice.
2. **Optimise or Eliminate `q _ pre-cmd`:** The 11ms delay introduced by `q _ pre-cmd` is unacceptable for a pre-command hook. This command needs to be significantly optimised, or its functionality should be achieved through a different mechanism that doesn't impact prompt responsiveness. Ideally, provide an option to disable this hook if the user doesn't require its functionality.
3. **Investigate `q _ should-figterm-launch`:** While less critical than `q _ pre-cmd`, the 13ms delay of `q _ should-figterm-launch` still contributes to startup time.
4. **Consider a Shell Script for `q` (Optional):** If feasible, consider providing at least parts of the `q` functionality as a shell script (rather than a compiled binary). This would allow users to more easily debug performance issues and potentially contribute improvements.
5. **Provide Clear Documentation:** The documentation should clearly state which parts are run at start-up and which are run before every prompt.
The current implementation significantly degrades the user experience, making the shell feel sluggish and unresponsive.
The workarounds described above improve the situation, but they require manual intervention and may disable some Amazon Q functionality.
A proper fix within the Amazon Q CLI is needed and indicates the need for more robust testing and optimisation of the CLI's shell integration.
### Steps to reproduce
_No response_
### Environment
```yaml
q diagnostic
[q-details]
version = "1.7.1"
hash = "7d766a2d0580db62fae5a4bc2e6eb63946d4d08e"
date = "2025-03-14T22:08:03.470338Z (3d ago)"
variant = "full"
[system-info]
os = "macOS 15.3.2 (24D81)"
chip = "Apple M2 Max"
total-cores = 12
memory = "96.00 GB"
[environment]
cwd = "/Users/USER/Downloads/q-cli-slow"
cli-path = "/Users/USER/Downloads/q-cli-slow"
os = "Mac"
shell-path = "/opt/homebrew/Cellar/zsh/5.9/bin/zsh"
shell-version = "5.9"
terminal = "iTerm 2"
install-method = "unknown"
[env-vars]
QTERM_SESSION_ID = "afa917ee9dcf47bda9d32585f8425880"
Q_SET_PARENT_CHECK = "1"
Q_TERM = "1.7.1"
SHELL = "/opt/homebrew/bin/zsh"
TERM = "xterm-256color"
__CFBundleIdentifier = "com.googlecode.iterm2"
```
Contributor guide
Assessment
This issue has not been assessed yet.