Skip to content

Dicklesworthstone/misc_coding_agent_tips_and_scripts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Misc Coding Agent Tips and Scripts

Practical guides for AI coding agents, terminal customization, and development tooling. Each guide documents a real problem encountered during daily work, the solution that fixed it, and copy-paste configurations to replicate the setup.

Quick Reference

Guide Problem Solved Time to Set Up
Destructive Git Command Protection AI agent ran git checkout -- and destroyed uncommitted work 5 min
Post-Compact AGENTS.md Reminder Claude forgets project conventions after context compaction 2 min
Host-Aware Terminal Colors Can't tell which terminal is connected to production 5-15 min
WezTerm Persistent Sessions Remote terminal sessions die when Mac sleeps or reboots 20 min
WezTerm Mux Tuning for Agent Swarms Mux server becomes unresponsive with 20+ agents 5 min
Ghostty Terminfo for Remote Machines Numpad Enter shows [57414u garbage when SSH'd 2 min
macOS NFS Auto-Mount Have to manually mount remote dev server after every reboot 10 min
Budget 10GbE Direct Link File transfers crawl at 100MB/s through gigabit switch 30 min
MX Master Tab Switching Thumbwheel does horizontal scroll instead of something useful 10 min
Doodlestein Punk Theme Need a cyberpunk color scheme for Ghostty 1 min
Reducing Vercel Build Credits Automatic deployments burn through Pro plan credits 10 min
Claude Code Native Install Fix claude --version shows old version after native install 5 min
Claude Code MCP Config Fix MCP servers wiped out, need quick restore 2 min
Mirror Claude Code Skills Copy project skills to global ~/.claude/skills 2 min
Beads Setup Worktree errors when syncing Beads 5 min
Moonlight Streaming Remote desktop to Linux workstation with AV1 encoding 30 min
Vault HA Cluster Single Vault instance is a single point of failure 45 min
DevOps CLI Tools Clicking through web dashboards wastes time 15 min
Gemini CLI Crash + Retry Fix Gemini CLI crashes with EBADF and gives up after 3 retries 10 sec
Zellij Scroll Wheel Fix Mouse wheel triggers atuin instead of scrollback in Zellij over SSH 10 min
Encrypted GitHub Issues Need to receive sensitive security reports in public repos 2 min

AI Agent Safety

Destructive Git Command Protection

Origin story: An AI agent ran git checkout -- on files containing hours of uncommitted work from a parallel coding session. The files were recovered via git fsck --lost-found, but this prompted creating a mechanical enforcement system.

A Python hook for Claude Code that intercepts Bash commands and blocks destructive operations before they execute.

Blocked commands:

Command Why it's dangerous
git checkout -- <files> Discards uncommitted changes permanently
git reset --hard Destroys all uncommitted work
git clean -f Deletes untracked files
git push --force Overwrites remote history
rm -rf (non-temp paths) Recursive deletion

How it works: Claude Code's PreToolUse hook system runs the guard script before each Bash command. The script pattern-matches against known destructive commands and returns a deny decision with an explanation. Safe variants (like git checkout -b, git clean -n, rm -rf /tmp/...) are allowlisted.

Quick install

See the full guide for the automated installer. After running it:

# Restart Claude Code for hooks to take effect

Test that it works:

echo '{"tool_name": "Bash", "tool_input": {"command": "git checkout -- file.txt"}}' | \
  python3 .claude/hooks/git_safety_guard.py
# Should output: permissionDecision: deny

Full guide →


Post-Compact AGENTS.md Reminder

Problem: During long coding sessions, Claude Code compacts the conversation to stay within context limits. After compaction, Claude "forgets" project-specific conventions documented in AGENTS.md.

A bash hook that detects context compaction and injects a reminder for Claude to re-read AGENTS.md.

How it works: Claude Code's SessionStart hook fires with source: "compact" after compaction. The hook checks this field and outputs a reminder message that Claude sees immediately.

Quick install
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/misc_coding_agent_tips_and_scripts/main/install-post-compact-reminder.sh | bash
# Restart Claude Code after installation

Or manually create ~/.local/bin/claude-post-compact-reminder:

#!/usr/bin/env bash
set -e
INPUT=$(cat)
SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
if [[ "$SOURCE" == "compact" ]]; then
    cat <<'EOF'
<post-compact-reminder>
Context was just compacted. Please reread AGENTS.md to refresh your understanding of project conventions and agent coordination patterns.
</post-compact-reminder>
EOF
fi
exit 0

Then add to ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          { "type": "command", "command": "$HOME/.local/bin/claude-post-compact-reminder" }
        ]
      }
    ]
  }
}

Full guide →


Terminal Customization

Ghostty Terminfo for Remote Machines

When using Ghostty to SSH into remote servers, you might see garbage like [57414u when pressing numpad Enter. This happens because the remote system doesn't understand the Kitty keyboard protocol that Ghostty uses.

One-liner fix:

infocmp -x xterm-ghostty | ssh user@your-server 'mkdir -p ~/.terminfo && tic -x -o ~/.terminfo -'
Helper function for multiple servers

Add to ~/.zshrc:

ghostty_push_terminfo() {
  local host="$1"
  [[ -z "$host" ]] && { echo "Usage: ghostty_push_terminfo <host>" >&2; return 1; }
  infocmp -x xterm-ghostty | ssh "$host" 'mkdir -p ~/.terminfo && tic -x -o ~/.terminfo -'
}

Then: ghostty_push_terminfo ubuntu@dev-server

Full guide →


Host-Aware Color Themes

When you have terminals open to multiple servers, color-coding each connection prevents accidentally running commands on the wrong machine.

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  PURPLE         │ │  AMBER          │ │  CRIMSON        │
│  dev-server     │ │  staging        │ │  production     │
└─────────────────┘ └─────────────────┘ └─────────────────┘

Two approaches:

Ghostty/Shell WezTerm/Lua
Setup time ~5 min ~15 min
Works in Any terminal with OSC support WezTerm only
Tab bar theming No Yes
Gradient backgrounds No Yes
Session persistence No Yes (survives disconnects)
Ghostty quick setup

Add to ~/.zshrc:

my-server() {
  printf '\e]11;#1a0d1a\a\e]10;#e8d4f8\a\e]12;#bb9af7\a'  # purple theme
  ssh [email protected] "$@"
  printf '\e]111\a\e]110\a\e]112\a\e]104\a'               # reset on exit
}

The printf commands send OSC escape sequences that change terminal colors. \e]11;#1a0d1a\a sets background; \e]111\a resets it.

Full guide →


WezTerm Persistent Remote Sessions

Remote terminal sessions that survive Mac sleep, reboot, or power loss. Uses WezTerm's native multiplexing with wezterm-mux-server running on remote servers via systemd.

┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ 󰍹  Local    │  │ 󰒋  Dev      │  │ 󰒋  Staging  │  │ 󰻠  Workstation│
│  [3 tabs]    │  │  [3 tabs]    │  │  [3 tabs]    │  │  [3 tabs]     │
│  (fresh)     │  │ (persistent) │  │ (persistent) │  │ (persistent)  │
└──────────────┘  └──────────────┘  └──────────────┘  └───────────────┘

Key features:

Feature How It Works
Persistent sessions wezterm-mux-server on remote holds state; Mac just reconnects
Smart startup Doesn't accumulate tabs on restart (checks if remote has existing tabs)
Domain-specific colors Each server gets distinct gradient + tab bar theme
Better than tmux Native scrollback, single keybinding namespace, GPU rendering
Remote setup (per server)
# Create systemd user service
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/wezterm-mux-server.service << 'EOF'
[Unit]
Description=WezTerm Mux Server
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/wezterm-mux-server --daemonize=false
Restart=on-failure

[Install]
WantedBy=default.target
EOF

# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now wezterm-mux-server
sudo loginctl enable-linger $USER

Full guide →


WezTerm Mux Tuning for Agent Swarms

When running 20+ AI coding agents (Claude, Codex) simultaneously, the default wezterm-mux-server configuration can't keep up. Output buffers overflow, caches thrash, and connections time out, killing all your agent sessions.

This guide provides RAM-tiered performance profiles that trade memory for throughput:

Setting Default 512GB Profile Why It Helps
scrollback_lines 3,500 10,000,000 Agents produce massive output; don't truncate
mux_output_parser_buffer_size 128KB 16MB Batch-process output bursts instead of choking
mux_output_parser_coalesce_delay_ms 3ms 1ms Reduce accumulated lag on high-throughput output
shape_cache_size 1,024 65,536 Cache font shaping to avoid CPU spikes

Smart sizing: Uses linear interpolation based on actual RAM, not fixed tiers. A 200GB system gets proportionally scaled settings.

Bonus: Emergency rescue procedure to migrate agent sessions to tmux using reptyr when the mux server becomes unresponsive (saves ~50-70% of sessions).

Quick install
./wezterm-mux-tune.sh              # Auto-detect RAM, interpolate
./wezterm-mux-tune.sh --dry-run    # Preview settings
./wezterm-mux-tune.sh --ram 200    # Specific RAM amount
./wezterm-mux-tune.sh --profile 256  # Fixed profile
./wezterm-mux-tune.sh --restore    # Restore backup

Then restart the mux server:

pkill -9 -f wezterm-mux
wezterm-mux-server --daemonize

Full guide → | Install script →


MX Master Thumbwheel Tab Switching

The horizontal thumbwheel on Logitech MX Master mice is designed for horizontal scrolling, which most developers rarely use. This guide repurposes it for tab switching across terminals, editors, and browsers.

Setup:

  1. Install BetterMouse ($10 one-time)
  2. Map thumbwheel to Ctrl+Shift+Arrow:
    Thumbwheel <<  →  Ctrl+Shift+Left
    Thumbwheel >>  →  Ctrl+Shift+Right
    
  3. Add keybindings to each app (WezTerm, Ghostty, VS Code, etc.)

Companion script: bettermouse_config.py exports and imports BetterMouse settings as JSON for backup or sharing. Run with uv run bettermouse_config.py show to view your current config.

Why Ctrl+Shift+Arrow?
Shortcut Problem
Ctrl+Tab Can't rebind in Chrome
Cmd+[ / Cmd+] Used for navigation history
Cmd+Shift+[ / Cmd+Shift+] Used for tab switching in some apps, but not universal
Ctrl+Shift+Arrow Rarely used as a default; easy to rebind everywhere

Full guide →


Doodlestein Punk Theme for Ghostty

A vibrant cyberpunk-inspired color scheme for Ghostty featuring deep space black backgrounds with electric neon accents.

┌─────────────────────────────────────────────────────┐
│  Background: #0a0e14 (deep space black)             │
│  Foreground: #b3f4ff (electric cyan)                │
│  Cursor:     #ff00ff (hot magenta)                  │
│                                                     │
│  Palette highlights:                                │
│    Red:    #ff3366 → #ff6b9d (electric pink)        │
│    Green:  #39ffb4 → #6bffcd (neon teal)            │
│    Blue:   #00aaff → #66ccff (cyber blue)           │
│    Magenta:#ff00ff → #ff66ff (hot purple)           │
└─────────────────────────────────────────────────────┘

Quick install:

# Copy theme to Ghostty themes directory
mkdir -p ~/.config/ghostty/themes
cp doodlestein-punk-theme-for-ghostty ~/.config/ghostty/themes/

Usage: Add to your Ghostty config (~/.config/ghostty/config):

theme = doodlestein-punk-theme-for-ghostty
Full palette
Index Normal Bright Color Name
0/8 #1a1f29 #3d4f5f Black/Gray
1/9 #ff3366 #ff6b9d Red/Pink
2/10 #39ffb4 #6bffcd Green/Teal
3/11 #ffe566 #ffef99 Yellow
4/12 #00aaff #66ccff Blue/Cyan
5/13 #ff00ff #ff66ff Magenta
6/14 #00ffff #66ffff Cyan
7/15 #c7d5e0 #ffffff White

Theme file →


Zellij Scroll Wheel Fix

Problem: When SSH'd into a remote Linux machine running Zellij as the terminal multiplexer, the mouse scroll wheel sends arrow keys instead of scrolling the terminal buffer. If you use atuin for shell history, the up arrow opens a full-screen history TUI on every scroll.

This is a known Zellij limitation — Zellij receives proper mouse scroll events but converts them to arrow key presses. The workaround uses Hammerspoon on macOS to intercept scroll wheel events and convert them to Alt+Up/Alt+Down keystrokes, which Zellij handles as scroll commands.

Scroll wheel → Hammerspoon (macOS) → Alt+Up/Down → SSH → Zellij → ScrollUp/ScrollDown

Three components:

Component Where What It Does
Zellij keybinds Remote server Alt+Up/Alt+DownScrollUp/ScrollDown in locked mode
Hammerspoon Mac Scroll wheel → Alt+Up/Alt+Down when terminal is focused
Atuin flag Remote server --disable-up-arrow prevents stray arrows from triggering history
Quick Zellij config
keybinds clear-defaults=true {
    locked {
        bind "Ctrl g" { SwitchToMode "Normal"; }
        bind "Alt Up" { ScrollUp; }
        bind "Alt Down" { ScrollDown; }
    }
    // ...
}
Hammerspoon gotchas

Three things that will silently break your event tap:

  1. local variables get garbage collected by Lua, killing the tap after 30-90 min. Use global variables.
  2. Calling :post() or keyStroke() inside the callback makes it too slow. Return events as a table (second return value) instead.
  3. macOS tapDisabledByTimeout kills slow taps. Add a watchdog timer that re-enables it every 5 seconds.

The guide also covers why WezTerm's native mouse_bindings don't reliably work for this, and how to tune scroll sensitivity with smooth scrolling mice.

Full guide →


Remote Development

macOS NFS Auto-Mount

If you have a remote Linux workstation with projects at /data/projects, you can make it automatically mount on your Mac at boot with graceful retry logic.

What you get:

~/dev-projects → /Volumes/dev-server/projects → 10.0.0.50:/data/projects

Components:

Component Purpose
Mount script Retries with exponential backoff when server is offline
LaunchDaemon Runs at boot, re-mounts on network changes
Symlink Convenient ~/dev-projects path
Synthetic firmlink Optional /dev/projects root-level path
Quick setup
# Create mount script
sudo tee /usr/local/bin/mount-dev-nfs << 'EOF'
#!/bin/bash
REMOTE_HOST="10.0.0.50"
MOUNT_POINT="/Volumes/dev-server"
[ -d "$MOUNT_POINT" ] || mkdir -p "$MOUNT_POINT"
mount | grep -q "$MOUNT_POINT" && exit 0
ping -c 1 -W 1 "$REMOTE_HOST" &>/dev/null || exit 1
/sbin/mount_nfs -o resvport,rw,soft,intr,bg "$REMOTE_HOST:/data" "$MOUNT_POINT"
EOF
sudo chmod +x /usr/local/bin/mount-dev-nfs

# Create LaunchDaemon (see full guide for complete plist)
# Then: sudo launchctl load /Library/LaunchDaemons/com.local.mount-dev-nfs.plist

# Create symlink
ln -sf /Volumes/dev-server/projects ~/dev-projects

Full guide →


Budget 10GbE Direct Link

Connect your Mac directly to a Linux workstation with 10GbE for ~$90 total, achieving 800+ MB/s transfers.

Mac Mini M4                    Linux Workstation
┌─────────────┐                ┌─────────────────┐
│ Thunderbolt │◄──Cat6 $5────►│ Built-in 10GbE  │
│ 10GbE ~$85  │                │ (Aquantia)      │
└─────────────┘                └─────────────────┘
     Static IPs: 10.10.10.x | Speed: ~850 MB/s

What you need:

Component Cost
IOCREST Thunderbolt 10GbE adapter ~$85 (AliExpress)
Cat6 cable ~$5

Many high-end workstations (Threadripper PRO, EPYC) have unused 10GbE ports. The IOCREST adapter uses the same Aquantia chip as Mac Studio's built-in 10GbE.

Also includes:

  • SHA-256 verified file transfers with speed reporting
  • Clipboard sync between Mac and Linux (Wayland/X11)
  • Remote display wake-up commands
  • AI coding agent aliases (Claude, Gemini, Codex)

Full guide →


Development Tools

Claude Code Native Install Fix

After installing Claude Code via curl -fsSL https://claude.ai/install.sh | bash, you might still see the old version if a previous bun/npm installation is earlier in your PATH.

Symptoms:

  • claude --version shows old version
  • "Auto-update failed" errors
  • claude doctor shows "Currently running: unknown"

Fix:

# Use explicit path in aliases
alias cc='~/.local/bin/claude --dangerously-skip-permissions'

# Update alias uses native updater
alias uca='~/.local/bin/claude update'

# Remove stale symlinks
rm ~/.bun/bin/claude 2>/dev/null

Full guide →


Claude Code MCP Config Fix

Claude Code stores MCP server configurations in ~/.claude.json. These can get wiped out by fresh installs, updates, or config corruption. Instead of running the full MCP Agent Mail installer, use this lightweight script that only restores the MCP config.

One command fix:

fix_cc_mcp

What it restores:

Server Type Purpose
mcp-agent-mail HTTP Multi-agent coordination, messaging, file reservations
morph-mcp stdio AI-powered code search via warp_grep
Quick install
# Download and install the script
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/misc_coding_agent_tips_and_scripts/main/FIX_CLAUDE_CODE_MCP_CONFIG.md | \
  sed -n '/^SCRIPT$/,/^SCRIPT$/p' | sed '1d;$d' > ~/.local/bin/fix_cc_mcp
chmod +x ~/.local/bin/fix_cc_mcp

# Edit to add your Morph API key
nano ~/.local/bin/fix_cc_mcp

Or copy the script from the full guide.

Token discovery: The script automatically finds your bearer token from MCP_AGENT_MAIL_TOKEN env var, ~/mcp_agent_mail/.env, or existing ~/.claude.json.

Full guide →


Mirror Claude Code Skills

Claude Code skills are directories containing a SKILL.md file, stored in .claude/skills/. Project-local skills only work in that project, but global skills in ~/.claude/skills/ are available everywhere. This script mirrors skills from a project to the global directory using rsync.

Usage:

mirror_cc_skills                     # Mirror from current project
mirror_cc_skills /path/to/project    # Mirror from specific project
mirror_cc_skills --dry-run           # Preview changes
mirror_cc_skills --sync              # Delete skills not in source (backs up first)

Behavior:

Mode What it does
Default Only adds/updates skills, never deletes from destination
--sync Full sync: deletes skills not in source (creates timestamped backup first)
--dry-run Shows what would be copied without making changes
Quick install
# Download and install
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/misc_coding_agent_tips_and_scripts/main/mirror_cc_skills \
  -o ~/.local/bin/mirror_cc_skills
chmod +x ~/.local/bin/mirror_cc_skills

Or copy from this repo:

cp mirror_cc_skills ~/.local/bin/
chmod +x ~/.local/bin/mirror_cc_skills

Note: The script automatically installs gum on first run for prettier output (styled boxes and spinners). Works fine without it if installation fails.

Script source →


Beads Setup

Beads uses git worktrees for sync operations. If your sync.branch is set to your current branch, you'll get:

fatal: 'main' is already checked out at '/path/to/repo'

Fix: Create a dedicated sync branch:

git branch beads-sync main
git push -u origin beads-sync
bd config set sync.branch beads-sync

Full guide →


Gemini CLI Crash + Retry Fix

Google's Gemini CLI (@google/gemini-cli) has two bugs that make it nearly unusable in practice:

  1. EBADF crash on every launch (wide terminals): The CLI uses node-pty for shell execution. A useEffect in the shell tool component fires resizePty() after the PTY's file descriptor is already closed. The native C++ addon throws Error("ioctl(2) failed, EBADF"), but the catch blocks only check err.code === 'ESRCH' — the native addon sets no .code property (only .message), so the error falls through and crashes the entire CLI.

  2. "Sorry there's high demand" gives up after 10 tries: The default retry config (DEFAULT_MAX_ATTEMPTS = 10, maxDelayMs = 30000) means Gemini gives up quickly during high-demand periods. Worse, TerminalQuotaError (which fires for daily limits and temporary overload) bypasses retry entirely and immediately surrenders.

One-liner fix:

curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/misc_coding_agent_tips_and_scripts/main/fix-gemini-cli-ebadf-crash.sh | bash

What it patches (4 patches across 3 files, all idempotent):

Patch File Change
EBADF catch #1 shellExecutionService.js Add err.message?.includes('EBADF') to resizePty() catch
EBADF catch #2 ShellToolMessage.js Add EBADF + ESRCH checks to shell tool resize useEffect
Aggressive retry retry.js maxAttempts 10 → 1000, initialDelay 5s → 1s, maxDelay 30s → 5s
Never bail on quota retry.js TerminalQuotaError retries with backoff instead of immediately giving up
Other modes
./fix-gemini-cli-ebadf-crash.sh --check     # check if patches are needed (no changes)
./fix-gemini-cli-ebadf-crash.sh --verify    # reproduce the EBADF bug + show retry config
./fix-gemini-cli-ebadf-crash.sh --revert    # undo all patches
./fix-gemini-cli-ebadf-crash.sh --uninstall # same as --revert
How the EBADF bug was diagnosed
$ node -e "const pty = require('@lydell/node-pty-linux-x64/pty.node'); \
  try { pty.resize(-1, 80, 24); } catch(e) { \
    console.log('message:', e.message); \
    console.log('code:', e.code); \
    console.log('has code:', 'code' in e); }"

message: ioctl(2) failed, EBADF
code: undefined
has code: false

The native addon throws a plain Error with "EBADF" only in the message string. The existing catch checks err.code === 'ESRCH' which is undefined === 'ESRCH'false. The error falls through and crashes React's commit phase.

Note: Patches live in node_modules and will be overwritten by package updates. Re-run the script after bun update -g @google/gemini-cli.

Script source →


Reducing Vercel Build Credits

Vercel's automatic deployments on every push can burn through Pro plan credits quickly. Use the REST API to disable auto-deployments and take control of when builds happen.

Before:

git push → Vercel webhook → Build → Deploy → 💸 (every single push)

After:

git push → (nothing)
vercel --prod → Build → Deploy → 💸 (only when you're ready)

API command to disable auto-deploys:

curl -X PATCH "https://api.vercel.com/v9/projects/${PROJECT_ID}?teamId=${TEAM_ID}" \
  -H "Authorization: Bearer ${VERCEL_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"gitProviderOptions": {"createDeployments": "disabled"}}'
Additional optimizations
Setting API Field Effect
Disable auto-deploy gitProviderOptions.createDeployments No deploys on push/PR
Smart skip enableAffectedProjectsDeployments Skip unchanged monorepo projects
Custom check commandForIgnoringBuildStep Run script to decide

Full guide →


DevOps CLI Tools

Master the CLI for each cloud platform instead of clicking through web dashboards. This guide covers installation, authentication, and common commands for the tools you use daily.

Tool Purpose
gh GitHub PRs, issues, releases
vercel Deployments, logs, env vars
wrangler Cloudflare Workers, R2 storage
gcloud Google Cloud APIs, billing
supabase Database migrations, types
Quick install (all tools)
# GitHub CLI
brew install gh
gh auth login

# Vercel
bun add -g vercel
vercel login

# Cloudflare Wrangler
bun add -g wrangler
wrangler login

# Supabase
bun add -g supabase
supabase login

The guide also includes AGENTS.md blurbs for each tool with placeholders for your project-specific values.

Full guide →


Remote Desktop

Moonlight Streaming Configuration

Setup for streaming from a Linux workstation (Hyprland/Wayland, dual RTX 4090) to a Mac client using Moonlight with AV1 encoding.

Component Configuration
Server Sunshine on Hyprland with NVENC
Client Custom Moonlight build with AV1
Resolution 3072x1728 @ 30fps
Codec AV1 (requires RTX 40-series)
Shell aliases
ml      # Start Moonlight streaming
trj     # SSH into remote server
wu      # Wake up remote display
cptl    # Copy clipboard to Linux
cpfm    # Copy clipboard from Mac

Common issues: Display sleep disconnects GPU from DRM, causing "GPU doesn't support AV1" errors. Fix: enable NVIDIA persistence mode (nvidia-smi -pm 1) and disable hypridle.

Full guide →


Infrastructure

HashiCorp Vault HA Cluster

A highly available secrets manager using 3-node Raft consensus. If the leader fails, the cluster elects a new leader and keeps running.

┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
│   NODE 1         │   │   NODE 2         │   │   NODE 3         │
│   (Leader)       │   │   (Follower)     │   │   (Follower)     │
│   10.0.1.10      │   │   10.0.1.11      │   │   10.0.1.12      │
└────────┬─────────┘   └────────┬─────────┘   └────────┬─────────┘
         └──────────────────────┼──────────────────────┘
                          Raft Consensus

Why Integrated Raft:

Benefit Description
No external dependencies Storage built into Vault
Automatic failover Leader election in seconds
Consistent replication All nodes have the same data
Initialize cluster (first node only)
export VAULT_ADDR='http://127.0.0.1:8200'

# Initialize with Shamir's Secret Sharing
vault operator init -key-shares=5 -key-threshold=3

# Save the unseal keys and root token securely!
# You need 3 of 5 keys to unseal after restart.

# Unseal
vault operator unseal  # x3 with different keys
Check cluster health
vault operator raft list-peers
# Node     Address              State       Voter
# node1    10.0.1.10:8201       leader      true
# node2    10.0.1.11:8201       follower    true
# node3    10.0.1.12:8201       follower    true

Full guide →


Security

Encrypted GitHub Issues

Problem: You want to accept sensitive security reports (vulnerabilities, credentials, PII) through public GitHub issues without exposing the content to anyone except you and your coding agents.

The solution uses age, a modern X25519 elliptic-curve encryption tool. Reporters encrypt to your public key; only your private key can decrypt. No shared secrets, no out-of-band key exchange, no GPG complexity.

Coding agents: Run gh-issue-decrypt with no arguments for an interactive guide that walks through the encrypt/submit/scan/decrypt workflow and detects your local keys automatically.

Reporter                                 Your Coding Agent
────────                                 ─────────────────
1. Gets your public key from README      4. Scans issues (gh api)
2. Encrypts with age -a -r PUBKEY        5. Detects armored age blocks
3. Pastes ciphertext in GitHub issue     6. Decrypts with age -d -i key

One-liner install (sets up age, gh CLI, and generates your keypair):

curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/misc_coding_agent_tips_and_scripts/main/gh-issue-decrypt?$(date +%s)" | bash -s -- --install

For reporters submitting an encrypted issue:

# Install age if needed, then encrypt and submit in one command:
echo "SSRF vulnerability in /api/proxy — allows internal network scanning via user-controlled URL parameter" \
  | gh-issue-decrypt --encrypt age1YOUR_PUBKEY_HERE --submit OWNER/REPO --title "Security: SSRF in proxy endpoint"

Or manually, encrypt and paste:

echo "your secret report" | age -a -r age1PUBKEY_HERE
# Paste the output into a GitHub issue wrapped in [enc:age]...[/enc:age] markers
Feature Details
Encryption X25519 (Curve25519 ECDH), 128-bit security level
Key format age1... public key (62 chars, safe to publish)
Ciphertext ASCII-armored, Markdown-safe, paste-friendly
Dependencies age + gh (auto-installed on Linux/macOS)
Package managers apt, dnf, pacman, apk, zypper, nix, Homebrew, MacPorts, GitHub binary fallback
Agent integration --json mode for machine-readable output
Sender mode --encrypt encrypts stdin; --submit creates the issue directly
How reporters encrypt (step by step)
# 1. Install age
brew install age          # macOS
sudo apt install age      # Ubuntu/Debian
# Or let the script handle it:
gh-issue-decrypt --install

# 2. Get the project's public key from their README
# It looks like: age1k5fz7d7zqsd4k60rn024a3gsd4tumjrkaqna0r20jz4gsu7mmvvql2k3jr

# 3. Encrypt your message (armored for pasting)
echo "The auth middleware at line 42 skips token expiry validation
when the iss claim is missing. An attacker can forge expired JWTs
by omitting the issuer field." \
  | age -a -r age1k5fz7d7zqsd4k60rn024a3gsd4tumjrkaqna0r20jz4gsu7mmvvql2k3jr

# 4. Paste the output into a GitHub issue:
#
#   [enc:age]
#   -----BEGIN AGE ENCRYPTED FILE-----
#   YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
#   -----END AGE ENCRYPTED FILE-----
#   [/enc:age]
How your agents decrypt
# Scan all open issues in a repo
gh-issue-decrypt Dicklesworthstone/some_project

# Decrypt a specific issue
gh-issue-decrypt Dicklesworthstone/some_project 42

# Machine-readable output for automation
gh-issue-decrypt --json Dicklesworthstone/some_project

# Manual decryption of a single block
age -d -i ~/.config/age/issuebot.key <<'EOF'
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
-----END AGE ENCRYPTED FILE-----
EOF
Agent quickstart (run with no args)

Running gh-issue-decrypt with no arguments prints a guide covering the full workflow (encrypt, submit, scan, decrypt). Designed for agents like Claude Code, Codex, or Gemini CLI that need to understand the system from scratch:

gh-issue-decrypt
# Prints:
#   - How public-key encryption works in this context
#   - Step-by-step sender instructions
#   - Three submission methods (--submit, gh issue create, manual paste)
#   - Receiver scanning commands
#   - All available flags
Authentication caveat

age encrypts but does not authenticate the sender. Anyone with your public key can encrypt to you. If you need to verify who sent a message, have senders also sign their plaintext with:

  • SSH signatures (ssh-keygen -Y sign): easiest if they already have SSH keys
  • minisign (minisign -S): lightweight Ed25519 signing tool
Why age over GPG/minisign/libsodium
  • GPG/OpenPGP: Works but operationally fragile. Key management is a UX disaster. Even SOPS recommends age over PGP where possible.
  • minisign: Signing only, not encryption. Good complement to age for sender authentication.
  • libsodium/NaCl box: Good primitives but you end up designing a custom message format, detection convention, and CLI tooling yourself.
  • age: Simple CLI, tiny keys, Unix-composable, ASCII-armored output safe for Markdown, and the recipient-key model means no shared secrets.

Script source → | Full Claude Code session transcript → (the full Claude Code session that built this system: 19 human prompts, ~400 tool calls, from initial concept through fleet testing)


Tech Stack

Category Tools
AI Agents Claude Code, Codex, Gemini CLI
Terminals WezTerm, Ghostty, iTerm2
Editors VS Code, Zed
Version Control Git (with safety hooks)
Remote Access NFS, SSH, Moonlight, Sunshine
Infrastructure HashiCorp Vault, systemd
Platforms Vercel
Package Managers bun, npm, native installers
Hardware NVIDIA GPUs, Logitech MX Master
Security age (X25519 encryption)
OS macOS, Linux (Ubuntu, Arch)

Related


Last updated: March 2026

About

Various things I had to figure out recently to make things work better...

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages