Speed Up Git Bash + Oh My Zsh on Windows (66% Faster Startup) A developer documented cutting zsh startup time in Git Bash on Windows from 719 ms to 243 ms (a 66% reduction) by addressing MSYS2's roughly 20 ms per-process launch cost, which a stock Oh My Zsh plus Powerlevel10k setup paid around 30 times per startup. The fixes included updating Oh My Zsh, removing a duplicate compinit call, setting ZSH_DISABLE_COMPFIX, deduplicating PATH, and skipping Powerlevel10k's SSH probe, with the full bash-to-zsh chain dropping from 1071 ms to 648 ms. Fix slow zsh startup in Git Bash on Windows. Why Oh My Zsh and Powerlevel10k take over a second to load under MSYS2, how to measure it, and safe fixes that cut zsh startup from 719 ms to 243 ms without losing a single plugin. Keywords: oh my zsh slow startup windows, git bash zsh slow, powerlevel10k git bash windows, msys2 zsh startup time, zsh-defer, compinit slow, zprof, zsh profiling ⚠️ Warning: AI can make mistakes. Everything here changes your shell startup files ~/.zshrc , ~/.bashrc , your Oh My Zsh install . One wrong line can leave you without a working terminal. Back up first, read every change before it is applied, and keep a second terminal for example PowerShell open so you can roll back. If you use the AI prompts at the end, supervise every step and approve each change yourself. Opening a new Git Bash tab on Windows took about 1.1 seconds before you could type, even though a bare zsh starts in under 70 ms. The cause was not one slow plugin. On Windows, every external program a shell script starts costs about 20 ms , because MSYS2 has to emulate Unix fork and the antivirus inspects each launch . A stock Oh My Zsh + Powerlevel10k setup launched around 30 of them on every startup. On top of that came a duplicated compinit , a slow completion permission audit, plugins that ran before the first prompt, and stale lock folders left behind by killed shells. check: https://gist.github.com/fworks/af4c896c9de47d827d4caa6fd7154b6b https://gist.github.com/fworks/af4c896c9de47d827d4caa6fd7154b6b fworks/install-zsh-windows-git-bash.md - Windows 11, Windows Terminal and VS Code's integrated terminal - Git for Windows Git Bash , with MSYS2's zsh 5.9 copied into Git's /usr - ~/.bashrc hands over to zsh: if -t 1 ; then exec zsh; fi - Oh My Zsh + Powerlevel10k instant prompt enabled - Plugins: git , zsh-autosuggestions , zsh-syntax-highlighting , composer , artisan | Measurement median of 7 runs | Before | After | |---|---|---| | zsh startup: zsh -i -c exit | 719 ms | 243 ms −66% | | Full chain: bash -l -i -c 'exec zsh -i -c exit' | 1071 ms | 648 ms −40% | Your numbers will differ. Measure your own machine first. Bare zsh vs. your config repeat a few times and compare time zsh -f -c exit time zsh -i -c exit Cost of one process launch on your machine zsh -f -c 'zmodload zsh/datetime; t=$EPOCHREALTIME; repeat 100 command true; print "$ EPOCHREALTIME-t 10 ms per launch"' To see which functions are slow, temporarily put zmodload zsh/zprof on the first line of ~/.zshrc and zprof on the last. - Don't profile by pointing ZDOTDIR at another folder. Oh My Zsh then misses its completion cache, and you end up measuring a cold 6–8 s compinit . - zsh -i -c exit has no real terminal, so Powerlevel10k's instant prompt and Oh My Zsh's update check don't run there. Always confirm in a real tab. 1. Update Oh My Zsh first: omz update . Recent versions read git metadata and the completion cache directly, instead of starting git and grep on every launch. 2. Remove the duplicate compinit . Oh My Zsh already runs it, so a second call at the end of .zshrc re-scans every completion and re-runs the audit ~100 ms . If you added it for a custom completion folder, add that folder to fpath before Oh My Zsh loads instead. 3. ZSH DISABLE COMPFIX=true . Oh My Zsh then uses compinit -u and skips its insecure-folder filtering and warnings. On NTFS, Unix permissions are made up by MSYS2, so that check rarely means anything. Trade-off: you won't be warned if a completion folder becomes writable by other users. 4. Clean up PATH: typeset -U path fpath and path= $^path N-/ . Windows PATH is long and full of duplicates and dead folders. zsh scans every folder the first time it looks up commands. 5. Skip Powerlevel10k's SSH probe when you're not on SSH. p10k starts who just to detect SSH sessions, and a local terminal doesn't need that. 6. Turn off automatic update checks: zstyle ':omz:update' mode disabled . When a check is due, it can contact GitHub and stop at a Y/n prompt during startup. Closing the tab there leaves a lock behind. Run omz update yourself every few weeks. 7. Remove stale lock folders. A shell killed mid-startup can leave ~/.zcompdump- .lock or $ZSH/log/update.lock behind. The first one silently stops Oh My Zsh from ever recompiling its completion cache. Remove them with rmdir , which deletes empty folders only, and only while no other shell is starting. 8. Load non-essential plugins after the prompt with zsh-defer https://github.com/romkatv/zsh-defer . Autosuggestions, syntax highlighting and completion-only plugins don't need to exist before your first prompt, so zsh-defer loads them once the shell is idle. Powerlevel10k's instant prompt hides the delay, but your keystrokes still wait for startup to finish. 9. Keep third-party plugins in $ZSH CUSTOM/plugins , never in $ZSH/plugins . Oh My Zsh's .gitignore excludes custom/ , so omz update never touches your plugins or these changes. After changing plugins or fpath , the first new shell rebuilds the completion cache once, which takes a few seconds. Run zsh -i -c exit once to get it over with. Powerlevel10k instant prompt block stays at the very top export ZSH="$HOME/.oh-my-zsh" Startup speed: on Windows/MSYS every forked process costs ~20ms. ZSH DISABLE COMPFIX=true no insecure-dir filtering/warning NTFS perms are synthetic typeset -U path fpath drop duplicate PATH entries, first one wins path= $^path N-/ drop PATH dirs that don't exist p10k forks who to detect SSH; only needed when actually logged in over SSH -z $SSH CONNECTION$SSH CLIENT$SSH TTY && typeset -gx P9K SSH=0 P9K SSH TTY=$TTY ZSH THEME="powerlevel10k/powerlevel10k" zstyle ':omz:update' mode disabled run omz update manually plugins= git the rest are deferred, see the end of the file fpath+=~/.zfunc custom completions go on fpath BEFORE Oh My Zsh runs compinit source $ZSH/oh-my-zsh.sh -f ~/.p10k.zsh || source ~/.p10k.zsh Load non-essential plugins once the prompt is idle. source $ZSH CUSTOM/plugins/zsh-defer/zsh-defer.plugin.zsh zsh-defer source $ZSH/plugins/composer/composer.plugin.zsh zsh-defer source $ZSH CUSTOM/plugins/artisan/artisan.plugin.zsh zsh-defer source $ZSH CUSTOM/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh zsh-defer source $ZSH CUSTOM/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh keep last Install zsh-defer: git clone https://github.com/romkatv/zsh-defer "$ZSH CUSTOM/plugins/zsh-defer" Check the deferred plugins in a real terminal tab wait a second after it opens . It should print 1 1 : print $+functions zsh autosuggest start $+functions zsh highlight Rollback: back up first cp ~/.zshrc ~/.zshrc.bak , and restore with cp ~/.zshrc.bak ~/.zshrc . Still left on the table: Git for Windows' /etc/profile costs about 300 ms before zsh even starts, because ~/.bashrc runs exec zsh only after it. Trimming that means changing how your terminal launches the shell, which is out of scope here. Run the first prompt, review its report, then run the second one in the same session. Supervise every step. These prompts touch the files your terminal needs in order to start. You are diagnosing slow shell startup on my machine. This is READ-ONLY: do not edit, create, move or delete any file outside a temporary folder, and do not run updates or installers. My setup: Git Bash on Windows MSYS2 handing over to zsh with Oh My Zsh and possibly Powerlevel10k . Work fastest-first and show me each command before you run it: 1. Map the startup chain: the command my terminal launches Windows Terminal settings.json, VS Code terminal profile , what ~/.bashrc and ~/.bash profile do, $MSYSTEM, and the zsh, Git for Windows and Oh My Zsh versions. 2. Time it, 7 runs each, and report medians: zsh -f -c exit bare , zsh -i -c exit my config , bash -l -i -c "exec zsh -i -c exit" full chain . 3. Measure the cost of one process launch: time repeat 100 command true with $EPOCHREALTIME inside zsh -f . Also time git --version . 4. Profile ~/.zshrc with zprof WITHOUT editing it: make a temp folder whose .zshrc is ZDOTDIR=$HOME; zmodload zsh/zprof; source ~/.zshrc; zprof , then run ZDOTDIR=