Skip to content

Instantly share code, notes, and snippets.

@simoninglis
Created February 3, 2026 00:19
Show Gist options
  • Select an option

  • Save simoninglis/884482b8e808dc588779d4612c6adb90 to your computer and use it in GitHub Desktop.

Select an option

Save simoninglis/884482b8e808dc588779d4612c6adb90 to your computer and use it in GitHub Desktop.

Revisions

  1. simoninglis created this gist Feb 3, 2026.
    117 changes: 117 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    # CI Status in tmux Status Bar

    Put your CI build status in your tmux status bar. Know immediately when something breaks.

    ```
    C:✓ B:✓ D:✓ V:✓ │ 14:32 01-Feb
    ```

    ## Why?

    When you're doing trunk-based development, you need to know immediately when CI fails. Not 10 minutes later when you context-switch. Not at the end of the day.

    The best gate is one you can't forget to check. When it's in your peripheral vision all day, you're not going to forget.

    ## Scripts

    | Script | Platform | Requires |
    |--------|----------|----------|
    | `ci-status-github.sh` | GitHub Actions | [gh CLI](https://cli.github.com) |
    | `ci-status-gitea.sh` | Gitea Actions | [teax](https://github.com/simoninglis/teax) |

    ## Setup

    ### 1. Download the script

    ```bash
    # GitHub users
    curl -o ~/.local/bin/ci-status.sh \
    https://gist.githubusercontent.com/.../ci-status-github.sh
    chmod +x ~/.local/bin/ci-status.sh

    # Gitea users
    curl -o ~/.local/bin/ci-status.sh \
    https://gist.githubusercontent.com/.../ci-status-gitea.sh
    chmod +x ~/.local/bin/ci-status.sh
    ```

    ### 2. Configure the script

    Edit the script and set your repo:

    ```bash
    REPO="owner/repo"
    ```

    For Gitea with self-signed certs, uncomment:
    ```bash
    export TEAX_INSECURE=1
    ```

    ### 3. Add to tmux

    In your `.tmux.conf` or session script:

    ```bash
    # Simple - just CI status
    set -g status-right '#(~/.local/bin/ci-status.sh) │ %H:%M'

    # With refresh interval (default is 15s, this sets 30s)
    set -g status-interval 30
    ```

    ### 4. Per-project scripts

    For multiple projects, put the script in each project and reference it:

    ```bash
    # In your tmux session setup
    set -g status-right '#($PROJECT_DIR/scripts/ci-status.sh) │ %H:%M'
    ```

    ## Output

    | Output | Meaning |
    |--------|---------|
    | `CI:✓` | All workflows passed |
    | `CI:✗` | Something failed |
    | `CI:⏳` | Running |
    | `CI:-` | No runs found for this commit |
    | `CI:?` | Error (not in repo, API issue, etc.) |
    | `+3` | 3 unpushed commits (showing remote CI status) |

    ### Gitea multi-workflow output

    The Gitea script shows individual workflow status:

    ```
    C:✓ B:✓ D:✓ V:✓
    ```

    Where:
    - `C` = ci.yml (lint, test)
    - `B` = build.yml
    - `D` = staging-deploy.yml
    - `V` = staging-verify.yml

    Customize the `WORKFLOWS` variable to match your pipeline.

    ## Unpushed commit indicator

    When your local branch is ahead of remote, the scripts show what CI actually ran (remote HEAD) plus how many commits you haven't pushed:

    ```
    CI:✓ +3
    ```

    This means: "CI passed for what's on the server, but you have 3 local commits not yet pushed."

    ## Related

    - [teax](https://github.com/simoninglis/teax) - CLI for Gitea Actions (fills gaps in `tea`)
    - [gh CLI](https://cli.github.com) - GitHub's official CLI
    - Blog post: [Solo Dev Release Process](https://simoninglis.com/posts/solo-dev-release-process) - Why CI visibility matters

    ## License

    MIT
    50 changes: 50 additions & 0 deletions ci-status-gitea.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    #!/bin/bash
    # CI status for tmux status bar - Gitea Actions version
    # Requires: teax (https://github.com/simoninglis/teax)
    #
    # Usage: Add to tmux status bar:
    # set -g status-right '#(/path/to/ci-status-gitea.sh)'
    #
    # Output examples:
    # C:✓ B:✓ D:✓ V:✓ # All green
    # C:✓ B:✗ D:- V:- # Build failed
    # C:✓ B:✓ D:✓ V:✓ +3 # Green but 3 unpushed commits
    # CI:? # Error or not in git repo

    # Configuration - customize these
    REPO="owner/repo" # Your Gitea repo
    WORKFLOWS="C:ci.yml,B:build.yml,D:staging-deploy.yml,V:staging-verify.yml"

    # For self-signed certs, uncomment:
    # export TEAX_INSECURE=1

    cd "$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "CI:?"; exit 0; }

    LOCAL_SHA=$(git rev-parse --short HEAD 2>/dev/null) || { echo "CI:?"; exit 0; }
    REMOTE_SHA=$(git rev-parse --short origin/main 2>/dev/null) || { echo "CI:?"; exit 0; }

    get_ci_status() {
    local sha="$1"
    local result
    local exit_code

    result=$(teax -o tmux runs status -r "$REPO" --sha "$sha" --show "$WORKFLOWS" 2>/dev/null)
    exit_code=$?

    case $exit_code in
    0) echo "$result" ;; # All passed
    1) echo "$result" ;; # Some failed (still show status)
    2) echo "$result" ;; # Running
    3) echo "CI:-" ;; # No runs found
    *) echo "CI:?" ;; # Error
    esac
    }

    if [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then
    # Local ahead of remote - show remote status + unpushed count
    AHEAD_COUNT=$(git rev-list origin/main..HEAD --count 2>/dev/null || echo "?")
    STATUS=$(get_ci_status "$REMOTE_SHA")
    echo "${STATUS} +${AHEAD_COUNT}"
    else
    get_ci_status "$LOCAL_SHA"
    fi
    65 changes: 65 additions & 0 deletions ci-status-github.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,65 @@
    #!/bin/bash
    # CI status for tmux status bar - GitHub Actions version
    # Requires: gh CLI (https://cli.github.com)
    #
    # Usage: Add to tmux status bar:
    # set -g status-right '#(/path/to/ci-status-github.sh)'
    #
    # Output examples:
    # CI:✓ # Latest run passed
    # CI:✗ # Latest run failed
    # CI:⏳ # Running
    # CI:✓ +3 # Passed but 3 unpushed commits
    # CI:? # Error or not in git repo

    # Configuration - customize these
    REPO="owner/repo" # Your GitHub repo (or leave empty to auto-detect)

    cd "$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "CI:?"; exit 0; }

    # Auto-detect repo from git remote if not set
    if [ -z "$REPO" ]; then
    REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null) || { echo "CI:?"; exit 0; }
    fi

    LOCAL_SHA=$(git rev-parse --short HEAD 2>/dev/null) || { echo "CI:?"; exit 0; }
    REMOTE_SHA=$(git rev-parse --short origin/main 2>/dev/null) || { echo "CI:?"; exit 0; }

    get_ci_status() {
    local sha="$1"
    local status conclusion

    # Get the latest workflow run for this commit
    read -r status conclusion < <(gh run list --repo "$REPO" --commit "$sha" --limit 1 \
    --json status,conclusion -q '.[0] | "\(.status) \(.conclusion)"' 2>/dev/null)

    if [ -z "$status" ]; then
    echo "CI:-"
    return
    fi

    case "$status" in
    completed)
    case "$conclusion" in
    success) echo "CI:✓" ;;
    failure) echo "CI:✗" ;;
    cancelled) echo "CI:⊘" ;;
    *) echo "CI:?" ;;
    esac
    ;;
    in_progress|queued|waiting)
    echo "CI:⏳"
    ;;
    *)
    echo "CI:?"
    ;;
    esac
    }

    if [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then
    AHEAD_COUNT=$(git rev-list origin/main..HEAD --count 2>/dev/null || echo "?")
    STATUS=$(get_ci_status "$REMOTE_SHA")
    echo "${STATUS} +${AHEAD_COUNT}"
    else
    get_ci_status "$LOCAL_SHA"
    fi