• Desktop notification that takes you to the tmux pane that notified

    After a bit of searching I didn’t find any obvious solution to the problem of: I want a process running in a tmux pane to send a desktop notification, and when I click that notification I’m taken directly to the source pane.

    Here are a couple scripts that will achieve it (courtesy of opencode and the new codex-5.3 model).

    It’s tailored to my setup:

    • macos
    • Kitty terminal emulator
    • tmux running locally

    Example invocation:

    ./tmux-notify-return.sh <title> <message>
    

    It’s probably a little brittle, but I think it will address a pain point I’ve been facing with a flurry of agents running across different tmux windows and panes.

    Here are the scripts:

    tmux-return.sh

    What it does: click-handler script run by Notification Center; activates Kitty, switches the correct tmux client/window/pane, and retries briefly to avoid focus races.

    #!/usr/bin/env bash
    set -euo pipefail
    
    if [[ $# -lt 4 || $# -gt 5 ]]; then
      echo "Usage: $0 <client_tty> <socket_path> <target_win> <target_pane> [target_pane_id]" >&2
      exit 2
    fi
    
    client_tty="$1"
    socket_path="$2"
    target_win="$3"
    target_pane="$4"
    target_pane_id="${5:-}"
    
    TMUX_BIN="/opt/homebrew/bin/tmux"
    OSA_BIN="/usr/bin/osascript"
    
    "$OSA_BIN" -e 'tell application id "net.kovidgoyal.kitty" to activate'
    
    for _ in 1 2 3 4 5 6 7 8 9 10; do
      "$TMUX_BIN" -S "$socket_path" switch-client -c "$client_tty" -t "$target_win" || true
      "$TMUX_BIN" -S "$socket_path" select-window -t "$target_win" || true
    
      if [[ -n "$target_pane_id" ]]; then
        "$TMUX_BIN" -S "$socket_path" select-pane -t "$target_pane_id" || true
      else
        "$TMUX_BIN" -S "$socket_path" select-pane -t "$target_pane" || true
      fi
    
      if [[ -n "$target_pane_id" ]]; then
        current_pane="$("$TMUX_BIN" -S "$socket_path" display-message -p -c "$client_tty" '#{pane_id}' 2>/dev/null || true)"
        [[ "$current_pane" == "$target_pane_id" ]] && exit 0
      else
        current_pane="$("$TMUX_BIN" -S "$socket_path" display-message -p -c "$client_tty" '#{session_name}:#{window_index}.#{pane_index}' 2>/dev/null || true)"
        [[ "$current_pane" == "$target_pane" ]] && exit 0
      fi
    
      sleep 0.1
    done
    
    exit 1
    

    tmux-notify-return.sh

    What it does: captures the tmux pane running this command (TMUX_PANE), then sends a clickable notification whose click runs tmux-return.sh back to that captured pane.

    #!/usr/bin/env bash
    set -euo pipefail
    
    # Send a clickable macOS notification that returns you to the tmux pane
    # where this command was run.
    TMUX_BIN="/opt/homebrew/bin/tmux"
    NOTIFIER_BIN="/opt/homebrew/bin/terminal-notifier"
    RETURN_SCRIPT="/path/to/tmux-return.sh"
    
    if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
      echo "Usage: $0 [title] [message]"
      echo "  title   Notification title (default: Return to tmux pane)"
      echo "  message Notification body  (default: Click to jump back in Kitty)"
      exit 0
    fi
    
    title="${1:-Return to tmux pane}"
    message="${2:-Click to jump back in Kitty}"
    
    if [[ ! -x "$TMUX_BIN" ]]; then
      echo "tmux not found at $TMUX_BIN" >&2
      exit 1
    fi
    
    if [[ ! -x "$NOTIFIER_BIN" ]]; then
      echo "terminal-notifier not found at $NOTIFIER_BIN" >&2
      exit 1
    fi
    
    if [[ ! -x "$RETURN_SCRIPT" ]]; then
      echo "return script not executable: $RETURN_SCRIPT" >&2
      exit 1
    fi
    
    # Capture the pane that is actually running this process when possible.
    # In tmux shells, TMUX_PANE is stable even if the user navigates away later.
    source_pane="${TMUX_PANE:-}"
    
    if [[ -n "$source_pane" ]]; then
      target_win="$("$TMUX_BIN" display-message -p -t "$source_pane" '#S:#I')"
      target_pane="$("$TMUX_BIN" display-message -p -t "$source_pane" '#S:#I.#P')"
      target_pane_id="$("$TMUX_BIN" display-message -p -t "$source_pane" '#{pane_id}')"
    else
      target_win="$("$TMUX_BIN" display-message -p '#S:#I')"
      target_pane="$("$TMUX_BIN" display-message -p '#S:#I.#P')"
      target_pane_id="$("$TMUX_BIN" display-message -p '#{pane_id}')"
    fi
    
    client_tty="$("$TMUX_BIN" display-message -p '#{client_tty}')"
    socket_path="$("$TMUX_BIN" display-message -p '#{socket_path}')"
    
    if [[ -z "$client_tty" || -z "$socket_path" || -z "$target_win" || -z "$target_pane" || -z "$target_pane_id" ]]; then
      echo "failed to capture tmux target metadata" >&2
      exit 1
    fi
    
    printf -v q_client '%q' "$client_tty"
    printf -v q_socket '%q' "$socket_path"
    printf -v q_win '%q' "$target_win"
    printf -v q_pane '%q' "$target_pane"
    printf -v q_pane_id '%q' "$target_pane_id"
    
    exec_cmd="$RETURN_SCRIPT $q_client $q_socket $q_win $q_pane $q_pane_id"
    
    "$NOTIFIER_BIN" \
      -title "$title" \
      -subtitle "$target_pane" \
      -message "$message" \
      -execute "$exec_cmd"
    

  • Agent harness improvements

    Just logging for posterity that AI models and specifically coding agent harnesses have gotten really good at discovering and using tools lately.

    Some of my recentish posts are already dated and seem obvious now:

    I’ve been having this feeling on and off since 2022, but I need to check most impulses to build any significant low-level (close to the model) customization on top of what’s offered. It’s invariably only a matter of (short) time until the foundation models overcome the next crop of limitations and the harnesses fill more ergonomic gaps. It feels like Skills are getting to a good place, but there’s still quite a lot of thrash around security best practices, MCP vs direct CLI/SDK/cURL, orchestration, and agent to agent communication.


  • Agentic git bisect

    Git bisect is a git command to find when some property in your codebase changed, e.g. when a bug was introduced or when a variable was first used.

     This command uses a binary search algorithm to find which commit in your project’s history introduced a bug. You use it by first telling it a “bad” commit that is known to contain the bug, and a “good” commit that is known to be before the bug was introduced. Then git bisectpicks a commit between those two endpoints and asks you whether the selected commit is “good” or “bad”. It continues narrowing down the range until it finds the exact commit that introduced the change.

    In fact, git bisect can be used to find the commit that changed any property of your project; e.g., the commit that fixed a bug, or the commit that caused a benchmark’s performance to improve.

    In the hands of an AI agent coding assistant like Claude Code, git bisect can quickly find that property change event for you with very little intervention. Connect it to tools like Jira and GitHub and code archaeology is much faster.


  • MacWhisper

    After sparse but longtime frustration with macOS’s built-in dictation (doesn’t input in every context, transcription is mid), I finally installed MacWhisper. After just a few minutes of setup it already seems much better than the built-in option. Obligatory gen AI commentary: yes I am using it for vibecoding. This post isn’t dictated, though – my partner is watching TV beside me. Next step: BCI.


  • Coding with agent helper scripts

    Lately I’ve been doing more AI agent assisted coding and vibecoding using Cursor and Claude Code. Oftentimes the agent will need some feedback about how a particular approach is working. This can involve me running the program, going through a particular UX flow, and then reporting the results back to the agent. Such manual verification is time consuming and kind of tedious when there are multiple rounds of back and forth.

    A technique that helps is to prompt the agent to write a small helper script to test some functionality — like interfacing with an API or database or evaluating an algorithm with particular inputs. The agent can then invoke that small script itself quickly, view debug output, and plan its next move. It takes me out of that loop and makes the whole experience smoother. It’s not quite a unit test or full integration test, just some throwaway code to give the agent a feeler into the environment.