Skip to content

bug: Amazon Q CLI Significantly degrades ZSH performance #844

@sammcj

Description

@sammcj

Checks

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:

      time $HOME/.local/bin/q _ pre-cmd --alias "$(\alias)"

      Output:

      $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:

      time $HOME/.local/bin/q _ should-figterm-launch

      Output:

      ❌ 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:

    # 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.

      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 evaling 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:

${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

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"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions