From a06bb371c13430ae796026a1ab756f6c500f04f2 Mon Sep 17 00:00:00 2001 From: David Ham <64705+davidham@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:01:59 -0500 Subject: [PATCH 1/3] fix(shell): rename loop var to avoid clobbering reserved module_path The module loader used module_path as its loop variable, but that is a reserved zsh special parameter (the array tied to MODULE_PATH used to locate loadable .so modules). Assigning to it and then unsetting it broke zmodload for the rest of the session, producing errors from fzf's completion.zsh (zsh/regex), compinit (zsh/complete), and Ghostty's shell integration (zsh/zleparameter). Rename it to module_file. --- home/zshrc.zsh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/home/zshrc.zsh b/home/zshrc.zsh index 05a9295..1d20903 100644 --- a/home/zshrc.zsh +++ b/home/zshrc.zsh @@ -8,11 +8,18 @@ _DOTFILES_SHELL_DIR="${${(%):-%N}:A:h:h}/shell" # Source order matters: env.zsh first (PATH, EDITOR, XDG vars), then # everything else. +# +# Note: do NOT name the loop var `module_path` -- that's a reserved zsh +# special parameter (the array tied to MODULE_PATH used to locate +# loadable .so modules). Overwriting and then unsetting it breaks +# zmodload for the rest of the session, which produces errors from +# fzf's completion.zsh (zsh/regex), compinit (zsh/complete), and +# Ghostty's shell integration (zsh/zleparameter). for module in env aliases functions prompt fzf; do - module_path="${_DOTFILES_SHELL_DIR}/${module}.zsh" - [[ -r $module_path ]] && source "$module_path" + module_file="${_DOTFILES_SHELL_DIR}/${module}.zsh" + [[ -r $module_file ]] && source "$module_file" done -unset module module_path +unset module module_file # Machine-local override (gitignored). Sourced last so it can override # anything above. From 5cf52229a9a91a0ce5b78421dc950c342344788a Mon Sep 17 00:00:00 2001 From: David Ham <64705+davidham@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:02:15 -0500 Subject: [PATCH 2/3] fix(prompt): stop reset-prompt from clobbering command output zle reset-prompt has geometry quirks when the prompt wraps, and the redraw triggered from TRAPUSR1 could clear into the previous command's output. Two changes prevent this: - Only redraw when the rendered git status actually changed. Consecutive commands in the same repo usually produce identical status, so skipping those redraws avoids the glitch entirely. - Print the blank line above the prompt from a precmd hook instead of a leading newline in PS1. The leading newline put the recorded prompt-start position one line above the prompt content, which combined with Ghostty's fresh-line behavior let reset-prompt clear the line above. An accept-line wrapper forces precmd to run on a bare Enter so the spacing stays consistent. --- shell/prompt.zsh | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/shell/prompt.zsh b/shell/prompt.zsh index ea8e86c..1257c4f 100644 --- a/shell/prompt.zsh +++ b/shell/prompt.zsh @@ -140,8 +140,18 @@ TRAPUSR1() { dir=${content%%$'\t'*} rendered=${content#*$'\t'} if [[ $dir == $PWD ]]; then - _PROMPT_GIT_STATUS=$rendered - zle && zle reset-prompt + # Only redraw if the rendered status actually changed. zle + # reset-prompt has geometry quirks when the prompt wraps (long + # branch names in big repos), and the redraw can clobber the + # blank line above the prompt. Most consecutive commands in + # the same repo produce identical status -- skipping the + # redraw for those avoids the issue entirely. Only the first + # command after chpwd will trigger a redraw and possibly hit + # the glitch. + if [[ $_PROMPT_GIT_STATUS != $rendered ]]; then + _PROMPT_GIT_STATUS=$rendered + zle && zle reset-prompt + fi else # Result is for an old PWD; kick a fresh compute for the current dir. _prompt_git_kick @@ -156,6 +166,26 @@ autoload -Uz add-zsh-hook add-zsh-hook precmd _prompt_git_kick add-zsh-hook chpwd _prompt_git_chpwd +# Print a blank line before each prompt for visual separation between +# commands. This was previously done with a leading $'\n' inside PS1, +# but that put the recorded prompt-start position one line above the +# actual prompt content. Combined with Ghostty's OSC 133;A;cl=line +# fresh-line behavior, `zle reset-prompt` from TRAPUSR1 would clear +# into the previous command's output. Printing the blank line here, +# outside PS1, keeps the prompt-start position on the user-info line +# so reset-prompt can never touch the command output above. +_prompt_blank_line() { print; } +add-zsh-hook precmd _prompt_blank_line + +# The default accept-line widget skips precmd when the buffer is +# empty, which means _prompt_blank_line above never runs for a bare +# Enter and there's no visual separation between successive prompts. +# Wrapping accept-line (even with a no-op body) forces the full +# command cycle including precmd, so _prompt_blank_line fires and we +# get the same blank line we get after a real command. +_prompt_accept_line() { zle .accept-line; } +zle -N accept-line _prompt_accept_line + # Backwards compat: anything else that calls prompt_git just gets the cached # value. The PS1 below references $_PROMPT_GIT_STATUS directly. function prompt_git() { @@ -181,8 +211,10 @@ else fi; # Set the terminal title and prompt. +# The blank line above the prompt is printed by the _prompt_blank_line +# precmd hook above -- do NOT add a leading $'\n' to PS1 (see comment +# there for why). PS1="${bold}"; -PS1+=$'\n'; PS1+="${userStyle}%n"; # username PS1+='${blue} (aws: $(aws_profile))'; PS1+="${white} at "; From 6db8b3c59a49b9c51d05e2959804685b1d09825c Mon Sep 17 00:00:00 2001 From: David Ham <64705+davidham@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:02:23 -0500 Subject: [PATCH 3/3] docs(shell): document 1Password lazy-load pattern for secrets Replace the plaintext GITHUB_TOKEN placeholder in the local config template with a commented example showing how to load it lazily from 1Password via op read, fetching only on first use and caching for the session. --- shell/local.zsh.example | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/shell/local.zsh.example b/shell/local.zsh.example index 8439154..5f6c078 100644 --- a/shell/local.zsh.example +++ b/shell/local.zsh.example @@ -13,5 +13,25 @@ # export SOMETHING=value # --- Per-machine secrets --- -# Prefer 1Password CLI, AWS Secrets Manager, etc. over plaintext here. -# export GITHUB_TOKEN=... +# Never put plaintext secrets here. Prefer a secret manager (1Password CLI, +# AWS Secrets Manager, etc.) and load secrets lazily so they are fetched +# only when needed -- not on every shell startup. +# +# Example: load GITHUB_TOKEN from 1Password the first time you run `gh`. +# The token lives in 1Password (op:////); it is cached +# in $GITHUB_TOKEN for the rest of the session after one fetch. Requires the +# 1Password CLI (`brew install 1password-cli`) with desktop-app integration. +# +# _OP_GITHUB_TOKEN_ACCOUNT="my.1password.com" # omit --account if single-account +# _OP_GITHUB_TOKEN_REF="op://Private/GitHub CLI token/credential" +# +# _load_github_token() { +# [[ -n $GITHUB_TOKEN ]] && return 0 +# command -v op >/dev/null 2>&1 || { print -u2 "op (1Password CLI) not found"; return 1; } +# local _tok +# _tok="$(op read --account "$_OP_GITHUB_TOKEN_ACCOUNT" "$_OP_GITHUB_TOKEN_REF" 2>/dev/null)" \ +# || { print -u2 "failed to read GITHUB_TOKEN from 1Password"; return 1; } +# export GITHUB_TOKEN="$_tok" +# } +# +# gh() { _load_github_token || return 1; command gh "$@"; }