Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Last active June 8, 2026 20:41
Show Gist options
  • Select an option

  • Save eevmanu/53afd68d59c07754fbb7e879edf02196 to your computer and use it in GitHub Desktop.

Select an option

Save eevmanu/53afd68d59c07754fbb7e879edf02196 to your computer and use it in GitHub Desktop.
how to use gh pr to set the pr number in the ps1 prompt via starship shell prompt with caching to avoid multiple http hits on each prompt redraw

Yes, this is a very common problem with putting API calls in prompts! If you run it synchronously, your terminal freezes for a half-second every time you press Enter.

To fix this, we can create a smart wrapper script. The script will:

  1. Immediately read from a /tmp/ cache file so your prompt is instantaneous (zero lag).
  2. Fetch from GitHub in the background if the cache is missing or older than 24 hours.
  3. Provide a --refresh flag so you can manually update it right after opening a new PR.

Here is the complete setup to get this working perfectly:

1. Create the Caching Script

Create a new file for the script. A good standard place is ~/.local/bin/.

mkdir -p ~/.local/bin
nano ~/.local/bin/gh_pr_prompt.sh

Paste the following bash script inside:

#!/usr/bin/env bash

# Exit immediately if we aren't in a git repo
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
BRANCH=$(git branch --show-current 2>/dev/null)
[ -z "$BRANCH" ] && exit 0

# Create a safe, unique cache filename for this specific repo + branch
SAFE_REPO=$(basename "$REPO_PATH")
SAFE_BRANCH=$(echo "$BRANCH" | tr '/' '_')
CACHE_FILE="/tmp/starship_gh_pr_${SAFE_REPO}_${SAFE_BRANCH}"

# Handle manual cache refresh (e.g., when you just created a new PR)
if [ "$1" == "--refresh" ]; then
    echo "Fetching PR data from GitHub..."
    PR=$(gh pr view "$BRANCH" --json number -q .number 2>/dev/null)
    if [ -n "$PR" ]; then
        echo "$PR" > "$CACHE_FILE"
        echo "✅ Cache updated: PR #$PR"
    else
        echo "none" > "$CACHE_FILE"
        echo "✅ Cache updated: No PR found for this branch"
    fi
    exit 0
fi

# Function to fetch PR in the background so the prompt never hangs!
update_cache_in_background() {
    {
        PR=$(gh pr view --json number -q .number 2>/dev/null)
        if [ -n "$PR" ]; then
            echo "$PR" > "$CACHE_FILE"
        else
            echo "none" > "$CACHE_FILE"
        fi
    } >/dev/null 2>&1 &
}

# Check if cache is missing or older than 24 hours (1440 minutes)
if [ ! -f "$CACHE_FILE" ]; then
    # First time seeing this branch. Show loading dots and fetch in background.
    echo "" > "$CACHE_FILE"
    update_cache_in_background
elif [ -n "$(find "$CACHE_FILE" -mmin +1440 2>/dev/null)" ]; then
    # Cache is stale (> 24h). Touch it to reset timer so we don't spawn multiple jobs, then fetch.
    touch "$CACHE_FILE"
    update_cache_in_background
fi

# Read and output the cache for Starship
VAL=$(cat "$CACHE_FILE" 2>/dev/null)
if [ "$VAL" != "none" ] && [ "$VAL" != "" ] && [ -n "$VAL" ]; then
    echo "$VAL"
elif [ "$VAL" == "" ]; then
    echo "..."
fi

Make the script executable:

chmod +x ~/.local/bin/gh_pr_prompt.sh

2. Update Starship Configuration

Now, edit your ~/.config/starship.toml to use this new script instead of running gh directly:

[custom.gh_pr]
description = "Show GitHub PR number with background caching"
command = "~/.local/bin/gh_pr_prompt.sh"
when = "git rev-parse --is-inside-work-tree 2>/dev/null"
format = "with [🐈‍⬛ PR #$output]($style) "
style = "bold purple"

3. Add an Alias to Manually Refresh (Optional but Recommended)

Since the script only auto-updates once every 24 hours, you'll want a way to force an update when you actively open a new PR on your current branch.

Add this alias to your ~/.bashrc or ~/.zshrc:

alias pr-refresh="~/.local/bin/gh_pr_prompt.sh --refresh"

(Don't forget to run source ~/.bashrc or source ~/.zshrc after adding it!)

How this works in practice:

  • Zero Lag: The prompt will always draw instantly because it just reads a tiny text file in /tmp/.
  • First time on a branch: It will briefly display [🐈‍⬛ PR #...] on your prompt while it asks GitHub in the background. The next time you press Enter, it will have the number.
  • No PR exists: If you aren't working on a PR, the background task writes "none" to the cache, and the module hides itself completely so it doesn't clutter your prompt.
  • Forcing an update: Just type pr-refresh in your terminal and it will ping GitHub and update the cache file immediately.
#!/usr/bin/env bash
# https://gist.github.com/eevmanu/53afd68d59c07754fbb7e879edf02196
# Print help message
show_help() {
echo "Usage: $(basename "$0") [OPTIONS]"
echo ""
echo "Options:"
echo " -f, --force-refresh Force refresh the PR cache synchronously"
echo " -d, --debug Enable debug logging to /tmp/gh_pr_prompt.log"
echo " -h, --help Show this help message"
}
# Logging function
log_debug() {
if [[ "$GH_PR_DEBUG" == "true" ]]; then
# Use $$ for script PID and $BASHPID for subshell PID if different
echo "[$(date -Iseconds)] [pid $$] $1" >> "/tmp/gh_pr_prompt.log"
fi
}
# Get current git branch
get_git_branch() {
local branch
branch=$(git branch --show-current 2>/dev/null)
log_debug "get_git_branch: found branch='$branch'"
echo "$branch"
}
# Get repo path
get_repo_path() {
local repo_path
repo_path=$(git rev-parse --show-toplevel 2>/dev/null)
log_debug "get_repo_path: found repo_path='$repo_path'"
echo "$repo_path"
}
# Get cache filename
get_cache_filename() {
local repo_path="$1"
local branch="$2"
local safe_repo
local safe_branch
safe_repo=$(basename "$repo_path")
safe_branch=$(echo "$branch" | tr '/' '_')
local cache_file="/tmp/starship_gh_pr_${safe_repo}_${safe_branch}"
log_debug "get_cache_filename: cache_file='$cache_file'"
echo "$cache_file"
}
# Fetch PR data
fetch_pr_data() {
local branch="$1"
local pr
log_debug "fetch_pr_data: fetching for branch='$branch' using gh cli"
pr=$(gh pr view "$branch" --json number -q .number 2>/dev/null)
if [[ -n "$pr" ]]; then
log_debug "fetch_pr_data: got PR='$pr'"
echo "$pr"
else
log_debug "fetch_pr_data: no PR found (or error)"
echo "none"
fi
}
# Update cache synchronously
update_cache_sync() {
local branch="$1"
local cache_file="$2"
local pr_data
log_debug "update_cache_sync: starting"
pr_data=$(fetch_pr_data "$branch")
echo "$pr_data" > "$cache_file"
log_debug "update_cache_sync: updated cache file '$cache_file' with '$pr_data'"
echo "$pr_data"
}
# Update cache in background
update_cache_in_background() {
local branch="$1"
local cache_file="$2"
log_debug "update_cache_in_background: spawning background job"
{
log_debug "background_job: starting fetch"
local pr_data
pr_data=$(fetch_pr_data "$branch")
echo "$pr_data" > "$cache_file"
log_debug "background_job: finished, wrote '$pr_data' to cache"
} >/dev/null 2>&1 &
}
# Read cache
read_cache() {
local cache_file="$1"
local val
val=$(cat "$cache_file" 2>/dev/null)
log_debug "read_cache: read '$val' from '$cache_file'"
echo "$val"
}
main() {
local force_refresh=false
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-f|--force-refresh|--refresh)
force_refresh=true
shift
;;
-d|--debug)
# Export so the background subshell inherits the logging flag
export GH_PR_DEBUG="true"
shift
;;
-h|--help)
show_help
exit 0
;;
*)
shift
;;
esac
done
log_debug "=== Script started (force_refresh=$force_refresh) ==="
local repo_path
repo_path=$(get_repo_path)
if [[ -z "$repo_path" ]]; then
log_debug "main: Not in a git repo. Exiting."
exit 0
fi
local branch
branch=$(get_git_branch)
if [[ -z "$branch" ]]; then
log_debug "main: No current branch. Exiting."
exit 0
fi
local cache_file
cache_file=$(get_cache_filename "$repo_path" "$branch")
if [[ "$force_refresh" == true ]]; then
log_debug "main: performing synchronous force refresh"
local pr_data
pr_data=$(update_cache_sync "$branch" "$cache_file")
if [[ "$pr_data" != "none" ]]; then
echo "$pr_data"
fi
log_debug "=== Script finished ==="
exit 0
fi
if [[ ! -f "$cache_file" ]]; then
# First time seeing this branch
log_debug "main: Cache missing. Starting background fetch."
echo "⏳" > "$cache_file"
update_cache_in_background "$branch" "$cache_file"
echo "..."
log_debug "=== Script finished (cache missing) ==="
exit 0
fi
if [[ -n "$(find "$cache_file" -mmin +1440 2>/dev/null)" ]]; then
# Cache is stale
log_debug "main: Cache stale (>24h). Starting background fetch."
touch "$cache_file"
update_cache_in_background "$branch" "$cache_file"
else
log_debug "main: Cache is fresh."
fi
local val
val=$(read_cache "$cache_file")
if [[ "$val" != "none" && "$val" != "⏳" && -n "$val" ]]; then
log_debug "main: outputting PR '$val'"
echo "$val"
elif [[ "$val" == "⏳" ]]; then
log_debug "main: outputting loading state"
echo "..."
else
log_debug "main: outputting nothing (val='$val')"
fi
log_debug "=== Script finished ==="
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment