The Curious Case of the Duplicating GPG_TTY and the Hidden .zprofile Culprit (and Why My Terminal Was So Slow!)
Source: Dev.to

Every now and then, a seemingly small configuration quirk can turn into a head‑scratching mystery, especially in the world of dotfiles. I recently found myself in such a predicament: the line
export GPG_TTY=$(tty)
kept magically reappearing in my .zshrc file every time I opened a new terminal. What started as a minor annoyance quickly became a fascinating dive into shell startup logic, Oh My Zsh plugins, and the often‑overlooked nuances of dotfile management—culminating in a significant performance hit to my terminal’s startup speed.
The Problem: A Frustratingly Sluggish Start (and a Massive Secret in .zshrc)
It began with a subtle, then increasingly painful, realization: my terminal was taking forever to start. What used to be an instantaneous pop‑up was now a sluggish crawl of several seconds. I’d open Cursor, Terminal.app, or Tabby, and just… wait.
After days of frustration I opened my .zshrc. To my absolute horror, it had grown to include thousands of identical lines:
export GPG_TTY=$(tty)
Every single one of those redundant lines was being parsed and executed by Zsh each time I opened a new terminal session, turning my snappy startup into a frustrating wait.
The GPG_TTY environment variable is crucial for GnuPG (GPG) to know which terminal to use for passphrase prompts—essential for signing Git commits. So, while the line itself was important, its relentless duplication (at least 3 000 times!) was definitely not.
Initial Suspicions: Oh My Zsh Plugins
My first thought, like many Zsh users, went straight to Oh My Zsh and its myriad of plugins. I knew there was a gpg-agent plugin, and its README.md explicitly stated:
“Updates the
GPG_TTYenvironment variable before each shell execution.”
A quick grep confirmed the plugin contained the line export GPG_TTY=$TTY. This told me the plugin was indeed managing the variable. My initial theory was that the plugin might be flawed, constantly writing to my .zshrc. But plugins are usually sourced, not designed to write back to the user’s main configuration file. This was a critical distinction.
If the plugin was setting the variable, and I was also seeing it appended to .zshrc, it suggested a conflict: the plugin was doing its job, but something else was writing the redundant line.
The Hunt for the Writer
Knowing that the line was being written (not just sourced) was key. I systematically tried:
| Action | Result |
|---|---|
Remove the duplicate line from .zshrc | It reappeared |
Disable the gpg-agent plugin | The line still reappeared |
Comment out source $ZSH/oh-my-zsh.sh in .zshrc (disabling Oh My Zsh completely) | The line still reappeared |
This confirmed the culprit was outside of Oh My Zsh’s direct influence, meaning it was likely in another Zsh startup file or a system‑level script.
Zsh processes several startup files in a specific order:
~/.zshenv~/.zprofile(login shells)~/.zshrc(interactive shells)~/.zlogin(login shells, after.zshrc)
Given the login‑shell behavior, .zprofile became a prime suspect.
The Aha! Moment: .zprofile Reveals Its Secrets
Opening my ~/.zprofile file, I immediately spotted it:
# https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key
# Add GPG key
if [ -r ~/.zshrc ]; then
echo -e '\nexport GPG_TTY=$(tty)' >> ~/.zshrc
else
echo -e '\nexport GPG_TTY=$(tty)' >> ~/.zprofile
fi
There it was—a script, seemingly taken directly from GitHub’s documentation on managing commit‑signature verification, designed to add export GPG_TTY=$(tty) to my .zshrc.
Here’s where I have to admit my own fault. Looking back at the documentation, it was clear that this command was meant to be run once as a terminal command to initialize the configuration. Instead, I had mistakenly copied the logic and pasted it directly into my ~/.zprofile.
The fatal flaw: the script lacked idempotence. It performed no check to see if GPG_TTY was already present in ~/.zshrc. Every time my login shell started and sourced .zprofile, it dutifully checked if .zshrc was readable (which it always was) and then unconditionally appended the line. Because .zprofile runs on every new login session, it was effectively “spamming” my .zshrc with new exports each time I opened my IDE or logged in.
The Fix and the Lesson Learned
- Delete the duplicates – I went back to my
~/.zshrcand manually removed all the thousands of duplicateexport GPG_TTY=$(tty)lines. - Remove the offending block – I deleted the entire
if … else … fiblock from my~/.zprofile.
After that, opening a new terminal left my .zshrc pristine, GPG_TTY was still correctly set (thanks to the Oh My Zsh gpg-agent plugin), and most importantly, my terminal startup speed was back to being snappy!
Key Lessons Learned
- Idempotence is king in dotfiles. Any script that modifies a configuration file should first check if the change is actually needed. Without this, you invite bloat, confusion, and significant performance degradation.
- Understand your shell’s startup files. Knowing the order and purpose of
.zshenv,.zprofile,.zshrc,.zlogin, etc., is vital for effective dotfile management. - Don’t copy‑paste whole command snippets into startup files. Documentation often shows a one‑off command; if you embed that logic into a file that runs repeatedly, you must adapt it to be safe for repeated execution.
With those take‑aways in mind, my terminal is fast again, and my dotfiles are clean—until the next mystery appears!
Troubleshooting
Third‑party documentation can be a double‑edged sword: while GitHub’s guide is intended to be helpful, the specific script provided for Zsh users could lead to issues if executed more than once, especially if it’s placed in a file like .zprofile that runs frequently.
Trust the specialized tools: If you’re using a plugin (like Oh My Zsh’s gpg-agent) designed to manage a specific environment variable, it’s often best to let it handle the heavy lifting.
This little adventure taught me a lot about the intricacies of my own shell environment. While it was a frustrating few days, the satisfaction of finally solving the mystery and reclaiming my fast terminal startup was well worth it. Keep your dotfiles clean, and always be wary of scripts that append without checking!