tmux send-keys silently drops the final Enter

When dispatching commands between tmux sessions, tmux send-keys -t <target> '<command>' Enter exits 0 and the command text appears in the target's input buffer — but the Enter never commits. The agent sits idle. The dispatcher sees success. Nothing happened.

This hits hardest with long commands that trigger tmux's bracketed paste mode: the trailing Enter gets absorbed into the paste block instead of executing it. Two other failure modes: the target app is in a modal state (dialog, secondary prompt) that swallows Enter, or the input handler is loaded and drops the keystroke.

The fix is a verify-and-retry loop after every dispatch:

dispatch() {
  local target="$1" cmd="$2"

  # 1. Send
  tmux send-keys -t "$target" "$cmd" Enter

  # 2. Wait for the target to start processing
  sleep 3

  # 3. Verify it's actually working, not just staged
  if tmux capture-pane -t "$target" -p -S -10 | tail -10 \
      | grep -qE "Working|Exploring|Reading|Analyzing"; then
    return 0
  fi

  # 4. Retry — empty Enter kicks the paste buffer
  tmux send-keys -t "$target" "" Enter
  sleep 2

  # One more check, then give up
  if tmux capture-pane -t "$target" -p -S -10 | tail -10 \
      | grep -qE "Working|Exploring|Reading|Analyzing"; then
    return 0
  fi

  # 5. Fallback: write to file, let target poll for it
  echo "$cmd" > "/tmp/dispatch-${target##*.}.cmd"
  return 1
}

Cap retries at 2. If both fail, fall back to writing the command to a file the target reads on its own polling cycle — this sidesteps the pty entirely.

The broader lesson: any async channel where the sender gets a local exit code instead of a structured delivery ACK has this failure class. Pty pipes, shell sessions, anything interactive. If you're building multi-agent coordination over interactive channels, bake verification into the dispatch protocol and don't trust exit code 0.

Apply the same pattern in reverse for callbacks: workers should verify their "done" message was received by the dispatcher, not just fire-and-forget.

网友语录 - 第82期 - 人一定要学会两件事,一个是靠自己,一个是为自己。一定要保持独立

这里记录我的一周分享,通常在周六或周日发布。


懒聪 我们终其一生寻找的应该是,自己喜欢的生活和自己本该成为的人。所以多走点弯路没关系,花很多时间在路上也不要紧。只要你一直在成为自己的路上就行。命运的仁慈就在于,只要你往前走,它总是给路


One could say in the first quarter-century of my life, that while I was always fascinated by programming, I could never overcome the guilt of not really knowing whether the tool I am building right now isn’t already superceded by some much better implementation someone else has already written 30 or 40 years ago; I could write a TSV-aware search and replace, or I could find out about awk and solve that entire class of problems in one fell swoop, for example. My central conceit is that this is a trap.

You need to reinvent a couple of wheels to get to the edge of what we know about wheel-making, not a thousand wheels, and not zero; probably four or five is sufficient in most domains, maybe closer to twenty or thirty in the most epistemically rigorous and developed fields like mathematics or computer science. Each wheel you reinvent, and every directed question you ask along the way, will propel you faster to the true frontier than that same amount of time spend in idle study, or even five times that amount.

— Andrew Quinn, footnote on Replacing a 3 GB SQLite database with a 10 MB FST (finite state transducer) binary


Khyines 人一定要学会两件事,一个是靠自己,一个是为自己。一定要保持独立。

fomalhaut 门口摆放的书上写着一句话:“最重要的是你上路了,然后一直往下走。”


THE LESS YOU CARE,
THE MORE YOU LIVE.


Ctrl+Alt+KP 1 这里KP是哪个键?

Claude responded: KP 是 Keypad,就是键盘右边的数字小键盘(NumPad)上的数字键。

KP 是 Keypad,就是键盘右边的数字小键盘(NumPad)上的数字键。

所以 Ctrl+Alt+KP_1 就是 Ctrl + Alt + 小键盘的1,不是主键盘行上方的数字1。

如果你的键盘没有小键盘(比如笔记本),建议直接设成 Ctrl+Alt+1(主键盘数字)就好。


改掉坏习惯的第一步是列出你的坏习惯清单,然后选择一个坏习惯(莫贪心,就一个),设闹钟提醒也罢,让别人督促也罢。坚持逆坏习惯行事。直至无须任何提醒就能下意识的按好习惯行事为止。


每个成年人的脑海中都有一条看不见的线,线外的观点都是“异教邪说”。(刻板印象神圣不可侵犯...虽然这样当然是不对的)


我们不可能掌控所有事,但我们能够控制自己每天怎么过。关键是,人们常常根本不去选择,习惯左右了决大多数人的每一天。只要能有意识的让自己多多养成好习惯,你的人生就是成功的


选择一件每天都能干完的“苦差事”,每天在固定的时间完成它。是什么苦差事不重要,关键在于坚持下去。(可以是洗碗,或者散步,如果膝盖足够好,也可以是跑步,etc....久而久之,你会发现,苦差事其实是并不苦。甚至可以乐在其中....


Our past is not our potential. -- Marilyn Ferguson


警惕:只要有人带你走,你就懒得记路。共产党说领我们闹革命,导致今天的国民能独立思考者甚寡


管埋员 “真正的机会,都是不舒服的,你犹豫,你怀疑,你不敢上。 这才是机会的样子。等你觉得舒服的时候,机会已经变成风险了。 ”

AI coding CLIs all support a custom config directory

When you need a clean slate — testing, debugging, or isolating a project — each major AI coding CLI lets you override its config directory via an environment variable:

# Claude Code
CLAUDE_CONFIG_DIR=/tmp/claude-clean claude

# GitHub Copilot CLI
COPILOT_HOME=/tmp/copilot-clean copilot

# OpenAI Codex CLI
CODEX_HOME=/tmp/codex-clean codex

These replace the entire config directory (~/.claude/, ~/.copilot/, ~/.codex/), so existing sessions, permissions, and plugins won't carry over. Copy the default directory contents if you need to preserve anything.

Copilot also has a separate COPILOT_CACHE_HOME for marketplace cache and update packages — setting COPILOT_HOME alone won't relocate those.

One IP, Multiple HTTPS Domains on Nginx — SNI Solves It

In the HTTP era, virtual hosting was trivial: one IP serves many domains, the Host header tells them apart. HTTPS breaks this because TLS handshake happens before any HTTP header — the server must pick a certificate before knowing which domain the client wants.

SNI — the standard fix

Server Name Indication is a TLS extension where the client sends the target hostname during handshake. All modern clients support it. Nginx uses it automatically — just define separate server blocks:

server {
    listen 443 ssl;
    server_name a.com;
    ssl_certificate /etc/ssl/a.com.crt;
    ssl_certificate_key /etc/ssl/a.com.key;
}

server {
    listen 443 ssl;
    server_name b.com;
    ssl_certificate /etc/ssl/b.com.crt;
    ssl_certificate_key /etc/ssl/b.com.key;
}

Each domain gets its own certificate. Nginx routes based on SNI. Zero extra config needed.

When you'd rather use one certificate

For a handful of related domains, a SAN certificate (Subject Alternative Name) covers multiple names in one cert:

server {
    listen 443 ssl;
    server_name a.com b.com c.com;
    ssl_certificate /etc/ssl/multi.crt;
    ssl_certificate_key /etc/ssl/multi.key;
}

Let's Encrypt makes this painless:

certbot --nginx -d a.com -d b.com -d c.com

For lots of subdomains (*.example.com), a wildcard certificate is the way to go.

SNI + Let's Encrypt covers 99% of real-world setups — free and automatic.

Git alias could run in dash, not bash — even if `/bin/bash` exists

I Wrote a git alias using [[ ... ]] and =~. Works fine in my interactive bash. Run the alias and it explodes:

$ git co master
... Syntax error: "(" unexpected (expecting "then")

First instinct: "but I have bash installed":

$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1298416 ... /bin/bash
$ /bin/bash --version
GNU bash, version 5.2.37(1)-release ...

Doesn't matter. A git alias starting with ! is hardcoded to run under /bin/sh — it doesn't read $SHELL, doesn't care what your login shell is. On Debian/Ubuntu /bin/sh -> dash, and dash doesn't understand [[, =~, == or other bash extensions:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 ... /bin/sh -> dash
$ echo '[[ "a" == a* ]]' | /bin/sh
/bin/sh: 1: [[: not found

The source is run-command.c::prepare_shell_cmd() in git itself — it literally calls sh -c.

Two fixes:

Wrap in bash -c explicitly (minimal change, but the nested quoting inside an alias gets ugly fast):

co = "!bash -c 'f() { ...bash syntax... }; f \"$@\"' _"

Rewrite as POSIX sh (preferred). Common substitutions:

  • [[ "$x" == -* ]]case "$x" in -*) ... ;; esac
  • [[ "$x" =~ ^HEAD~ ]]case "$x" in HEAD~*) ... ;; esac
  • [[ "$a" == "$b" ]][ "$a" = "$b" ]
  • [[ -f foo ]][ -f foo ] (already POSIX, no reason to use [[)

Lesson: write git aliases as if dash is the only shell on the planet. Don't reach for [[ because it feels nicer — either wrap with bash -c or just use case.