From 5ed916b14f0f966dd80ac980ec686eb11c99d018 Mon Sep 17 00:00:00 2001 From: masashiono0611 Date: Tue, 28 Jul 2026 08:03:08 +0900 Subject: [PATCH 1/4] feat(send): --stdin and --body-file so bodies bypass the sender shell and argv (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Message bodies passed positionally go through the caller's shell first, so an agent composing the command string can execute $(...) or backticks by accident. Separately, MSYS2 silently truncates a native caller's argv at 8186 chars. Neither is interceptable inside send.sh, because both happen before it runs — but an input path that never touches argv sidesteps both. --stdin / --body-file read the body verbatim. The positional form is unchanged, and -- was added so a body that IS the literal string --stdin/--body-file/--force keeps working. Ambiguous invocations are rejected rather than silently resolved. Co-Authored-By: Claude Opus 5 --- README.md | 2 + SKILL.md | 13 +++ scripts/send.sh | 152 ++++++++++++++++++++++++++++++++++- tests/test_messaging.bats | 163 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6adc0455..8c271cc2 100644 --- a/README.md +++ b/README.md @@ -341,6 +341,8 @@ See [docs/opencode.md](docs/opencode.md) for full setup instructions. `send.sh` takes four positional arguments — ` ""` — plus an optional trailing `--force`. Quote the message so the shell sees it as one argument; an unquoted message with spaces will be misparsed. Both `from` and `to` must already be registered in ``; an unregistered name errors out (listing the currently registered names) instead of silently storing an undeliverable message. Pass `--force` to bypass this check for an intentional pre-registration send. +A positional message is parsed by your shell before agmsg sees it, so a body containing backticks, `$(...)`, or tricky quoting can be evaluated or mangled there. For those bodies, replace `""` with `--stdin` (feed it a heredoc) or `--body-file ` — both read the body verbatim, with no shell re-interpretation. + ## FAQ / Design notes **Is this MCP? Do I need an MCP server?** diff --git a/SKILL.md b/SKILL.md index 9d551f8a..6d88fee4 100644 --- a/SKILL.md +++ b/SKILL.md @@ -61,6 +61,19 @@ Do NOT manually edit config files. Always use join.sh. If the name was recently # Send a message (from/to must already be registered in ; add --force to bypass) ~/.agents/skills/agmsg/scripts/send.sh "" [--force] +# A positional message goes through YOUR shell before agmsg ever sees it — a +# quoted body containing backticks or $(...) can be silently evaluated or +# executed there (#378). If the message contains backticks, $, or quotes, +# prefer --stdin (heredoc) or --body-file instead of fighting the shell with +# escaping; a plain message with none of those is fine quoted normally. +~/.agents/skills/agmsg/scripts/send.sh --stdin <<'EOF' +message body goes here, verbatim — backticks, $(...), quotes all pass through untouched +EOF +~/.agents/skills/agmsg/scripts/send.sh --body-file /path/to/body.txt + +# A body that IS one of the flag names still works — put `--` before it. +~/.agents/skills/agmsg/scripts/send.sh -- --stdin + # Message history ~/.agents/skills/agmsg/scripts/history.sh [agent_id] [limit] diff --git a/scripts/send.sh b/scripts/send.sh index e0996041..a9f8433f 100755 --- a/scripts/send.sh +++ b/scripts/send.sh @@ -2,14 +2,144 @@ set -euo pipefail # Usage: send.sh [--force] +# or: send.sh --stdin [--force] +# or: send.sh --body-file [--force] +# +# #378: a message body passed positionally goes through the SENDER's shell +# before it ever reaches this script — an unescaped `$(...)` in a quoted +# body can execute, and backticks can be silently evaluated/emptied. On +# Windows/MSYS a positional body is additionally routed through MSYS's +# argv-conversion path (build_argv -> globify), which truncates silently at +# exactly 8186 bytes (fixed MAXPATHLEN 8192 buffer in glob.cc). --stdin and +# --body-file read the body verbatim from a file descriptor instead of +# argv, so neither hazard applies — no shell re-interpretation, no argv +# size limit. The positional form is unchanged and remains the default. -TEAM="${1:?Usage: send.sh [--force]}" +USAGE="Usage: send.sh [--force] + or: send.sh --stdin [--force] + or: send.sh --body-file [--force]" + +TEAM="${1:?$USAGE}" FROM="${2:?Missing from agent}" TO="${3:?Missing to agent}" -BODY="${4:?Missing message body}" +shift 3 + +MODE="positional" +BODY="" +BODY_FILE="" FORCE=0 -if [ "${5:-}" = "--force" ]; then + +if [ $# -eq 0 ]; then + echo "Error: missing message body. Provide it positionally, via --stdin, or via --body-file ." >&2 + exit 1 +fi + +case "$1" in + --) + # Option terminator: everything after it is the body, verbatim. Without + # this, adding --stdin/--body-file silently broke a body that happens to + # BE the literal string "--stdin" (or "--body-file"/"--force"), which was + # a perfectly valid positional body before this change. `--` is the + # standard escape hatch and keeps that case working. + shift + if [ $# -eq 0 ]; then + echo "Error: missing message body after '--'." >&2 + exit 1 + fi + BODY="$1" + shift + ;; + --stdin) + MODE="stdin" + shift + ;; + --body-file) + shift + if [ $# -eq 0 ]; then + echo "Error: --body-file requires a path argument." >&2 + exit 1 + fi + # A leading '-' means the "path" is actually another flag that got + # swallowed here (e.g. `--body-file --stdin`, `--body-file --force`) + # instead of tripping the ambiguity check below. Reject it the same way + # validate.sh rejects a team/agent name starting with '-' — it would be + # parsed as an option by downstream tools. A real file named like that + # still works via an explicit './-foo' or absolute path. + case "$1" in + -*) + echo "Error: --body-file's argument '$1' looks like a flag, not a path. To use a file whose name starts with '-', pass './$1' or an absolute path." >&2 + exit 1 + ;; + esac + MODE="file" + BODY_FILE="$1" + shift + ;; + --force) + echo "Error: missing message body before --force. Provide it positionally, via --stdin, or via --body-file ." >&2 + exit 1 + ;; + *) + BODY="$1" + shift + ;; +esac + +# Reject combining two input modes instead of silently picking one — e.g. a +# positional body followed by --stdin, or --stdin followed by --body-file. +if [ "${1:-}" = "--stdin" ] || [ "${1:-}" = "--body-file" ]; then + echo "Error: the message body was already given (positional argument, --stdin, or --body-file) — cannot also pass $1. Provide the body exactly one way." >&2 + exit 1 +fi + +if [ "${1:-}" = "--force" ]; then FORCE=1 + shift +fi + +if [ $# -gt 0 ]; then + echo "Error: unexpected extra argument(s) after the message: $*" >&2 + exit 1 +fi + +if [ "$MODE" = "positional" ] && [ -z "$BODY" ]; then + echo "Error: missing message body." >&2 + exit 1 +fi + +# Read the body verbatim (no word-splitting, no glob expansion). `IFS= read +# -r -d ''` slurps to EOF without stripping leading or trailing +# whitespace/newlines — deliberately: whatever bytes are on stdin or in the +# file land in the message exactly as given, including any trailing +# newline(s). If you don't want a trailing newline in the sent message, +# don't put one in the input. `read` returns non-zero at EOF even though it +# captured the data, so failure here is expected and ignored. +# +# Caveat shared with every other bash variable in this script (not new +# here): a NUL byte terminates both `read -d ''` and a bash string, so +# anything after one is lost. That is a shell-wide limit, not something +# --stdin/--body-file could relax — the old positional path had +# the exact same ceiling (argv strings can't hold NUL either). +if [ "$MODE" = "stdin" ]; then + IFS= read -r -d '' BODY || true + if [ -z "$BODY" ]; then + echo "Error: --stdin was given but no data was read from standard input." >&2 + exit 1 + fi +elif [ "$MODE" = "file" ]; then + if [ ! -f "$BODY_FILE" ]; then + echo "Error: --body-file '$BODY_FILE' does not exist or is not a regular file." >&2 + exit 1 + fi + if [ ! -r "$BODY_FILE" ]; then + echo "Error: --body-file '$BODY_FILE' is not readable." >&2 + exit 1 + fi + IFS= read -r -d '' BODY < "$BODY_FILE" || true + if [ -z "$BODY" ]; then + echo "Error: --body-file '$BODY_FILE' is empty." >&2 + exit 1 + fi fi SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -73,7 +203,21 @@ fi # team/agent name containing a single quote would otherwise break the INSERT # (correctness) or change its meaning (injection surface). _agmsg_sqlesc() { printf %s "$1" | sed "s/'/''/g"; } -INSERT="INSERT INTO messages (team, from_agent, to_agent, body) VALUES ('$(_agmsg_sqlesc "$TEAM")', '$(_agmsg_sqlesc "$FROM")', '$(_agmsg_sqlesc "$TO")', '$(_agmsg_sqlesc "$BODY")');" + +# #378: a command substitution — `$(...)` — always drops ALL of its trailing +# newlines, no matter how many there are. TEAM/FROM/TO can never contain a +# newline (validate.sh rejects control characters), but BODY legitimately +# can now that --stdin/--body-file read it verbatim, and plugging it into +# the INSERT below via a plain `$(_agmsg_sqlesc "$BODY")` would silently +# eat any trailing newline(s) the caller asked to send. Appending a +# non-newline sentinel before escaping moves the trailing newline(s) into +# the middle of the captured text (where command substitution does not +# touch them), then the sentinel is stripped back off — preserving BODY's +# trailing newline(s) exactly instead of losing them the same way TEAM/ +# FROM/TO's escaping still does (harmlessly, since those can't have any). +_BODY_SQL="$(_agmsg_sqlesc "${BODY}X")" +_BODY_SQL="${_BODY_SQL%X}" +INSERT="INSERT INTO messages (team, from_agent, to_agent, body) VALUES ('$(_agmsg_sqlesc "$TEAM")', '$(_agmsg_sqlesc "$FROM")', '$(_agmsg_sqlesc "$TO")', '$_BODY_SQL');" # Retry once after ensuring the schema. Under a concurrent first-write fan-out # (leader → N members against a fresh/override store), one process can see the diff --git a/tests/test_messaging.bats b/tests/test_messaging.bats index 7eb5d229..708f00fc 100644 --- a/tests/test_messaging.bats +++ b/tests/test_messaging.bats @@ -108,6 +108,169 @@ teardown() { [[ "$output" =~ "Sent to bob" ]] } +# --- send.sh: --stdin / --body-file (#378) --- +# +# A positional body goes through the sender's shell (backticks / $(...) can +# execute or vanish) and, on Windows, through MSYS's argv-conversion path +# (silent truncation at 8186 bytes). --stdin/--body-file bypass both by +# never touching argv. + +@test "send: -- keeps a body that is literally --stdin working (backwards compat)" { + # Before --stdin/--body-file existed, "--stdin" was just an ordinary body. + # Adding the flags must not silently reinterpret it. + run bash "$SCRIPTS/send.sh" testteam alice bob -- --stdin + [ "$status" -eq 0 ] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='bob';") + [ "$stored" = "--stdin" ] +} + +@test "send: -- keeps a body that is literally --force working, and --force after it still applies" { + run bash "$SCRIPTS/send.sh" testteam alice bob -- --force + [ "$status" -eq 0 ] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='bob';") + [ "$stored" = "--force" ] +} + +@test "send: -- with no body after it is an error, not an empty message" { + run bash "$SCRIPTS/send.sh" testteam alice bob -- + [ "$status" -ne 0 ] + [[ "$output" =~ "missing message body after" ]] +} + +@test "send: --stdin delivers a body containing backticks and \$(...) byte-identical" { + local body='price is `echo hi` and $(whoami) literally' + run bash -c "printf '%s' \"\$1\" | bash \"\$2\" testteam alice bob --stdin" _ "$body" "$SCRIPTS/send.sh" + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to bob" ]] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='bob';") + [ "$stored" = "$body" ] +} + +@test "send: --body-file delivers a body containing backticks and \$(...) byte-identical" { + local body='price is `echo hi` and $(whoami) literally' + local f="$TEST_SKILL_DIR/body.txt" + printf '%s' "$body" > "$f" + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file "$f" + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to bob" ]] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='bob';") + [ "$stored" = "$body" ] +} + +@test "send: --body-file delivers a body larger than 8192 chars complete (MSYS argv-truncation class, #378)" { + local f="$TEST_SKILL_DIR/big.txt" + # 9000 'a' characters — larger than MSYS's fixed 8192-byte glob.cc buffer + # (truncation point reported at 8186). Written directly to a file, so this + # never goes through argv regardless of platform. + printf 'a%.0s' $(seq 1 9000) > "$f" + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file "$f" + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to bob" ]] + local len + len=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT length(body) FROM messages WHERE to_agent='bob';") + [ "$len" -eq 9000 ] +} + +@test "send: --stdin preserves an explicit trailing newline byte-for-byte" { + # A plain `stored=$(sqlite3 ... SELECT body ...)` capture would strip ALL + # trailing newlines via command substitution on the assertion side too, + # so it would pass whether the stored body kept zero, one, or several + # trailing newlines — it would not actually test what this test claims. + # hex()/length() sidestep that: the exact byte sequence of "line1\nline2\n" + # is 6c696e65310a6c696e65320a (12 bytes), independent of shell capture. + run bash -c "printf 'line1\nline2\n' | bash \"\$1\" testteam alice bob --stdin" _ "$SCRIPTS/send.sh" + [ "$status" -eq 0 ] + local body_hex body_len + # sqlite3's hex() emits uppercase A-F. + body_hex=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT hex(body) FROM messages WHERE to_agent='bob';") + body_len=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT length(body) FROM messages WHERE to_agent='bob';") + [ "$body_hex" = "6C696E65310A6C696E65320A" ] + [ "$body_len" -eq 12 ] +} + +@test "send: positional body still works unchanged alongside the new flags" { + run bash "$SCRIPTS/send.sh" testteam alice bob "hello" + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to bob" ]] +} + +@test "send: --stdin composes with --force" { + run bash -c "printf 'hi' | bash \"\$1\" brandnewteam ghost nobody --stdin --force" _ "$SCRIPTS/send.sh" + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to nobody" ]] +} + +@test "send: rejects a positional body combined with --stdin instead of silently picking one" { + run bash "$SCRIPTS/send.sh" testteam alice bob "hello" --stdin + [ "$status" -ne 0 ] + [[ "$output" =~ "was already given" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +@test "send: rejects --stdin combined with --body-file instead of silently picking one" { + local f="$TEST_SKILL_DIR/body.txt" + printf 'x' > "$f" + run bash -c "printf 'y' | bash \"\$1\" testteam alice bob --stdin --body-file \"\$2\"" _ "$SCRIPTS/send.sh" "$f" + [ "$status" -ne 0 ] + [[ "$output" =~ "was already given" ]] +} + +@test "send: rejects --body-file without a path argument" { + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file + [ "$status" -ne 0 ] + [[ "$output" =~ "requires a path argument" ]] +} + +@test "send: rejects --body-file followed by another flag instead of swallowing it as the path" { + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file --stdin + [ "$status" -ne 0 ] + [[ "$output" =~ "looks like a flag" ]] + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file --force + [ "$status" -ne 0 ] + [[ "$output" =~ "looks like a flag" ]] +} + +@test "send: rejects a missing --body-file target with a clear error, not an empty message" { + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file "$TEST_SKILL_DIR/does-not-exist.txt" + [ "$status" -ne 0 ] + [[ "$output" =~ "does not exist" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +@test "send: rejects an empty --body-file with a clear error, not an empty message" { + local f="$TEST_SKILL_DIR/empty.txt" + : > "$f" + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file "$f" + [ "$status" -ne 0 ] + [[ "$output" =~ "is empty" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +@test "send: rejects empty stdin with a clear error, not an empty message" { + run bash -c "printf '' | bash \"\$1\" testteam alice bob --stdin" _ "$SCRIPTS/send.sh" + [ "$status" -ne 0 ] + [[ "$output" =~ "no data was read" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +@test "send: rejects an unexpected extra argument after the message" { + run bash "$SCRIPTS/send.sh" testteam alice bob "hello" extra + [ "$status" -ne 0 ] + [[ "$output" =~ "unexpected extra argument" ]] +} + # --- inbox.sh --- @test "inbox: shows no messages when empty" { From d2802dddf6a618effa1bd26166db2704a5336583 Mon Sep 17 00:00:00 2001 From: masashiono0611 Date: Wed, 29 Jul 2026 18:49:20 +0900 Subject: [PATCH 2/4] review: flip send.sh's canonical form and reject NUL instead of truncating (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the review on #507. B1 — make --stdin/--body-file the canonical spelling rather than an extra option alongside the positional form. Every first-party example now uses it and marks the positional form deprecated in the same place: README.md, README.ja.md (which the first commit left out of sync), SKILL.md, all nine driver templates, the grok-build delivery rule, the codex bridge's reply prompt, both session-start.sh monitor directives, and docs/building-on-agmsg.md. Each agent-facing surface states that a body an agent composes MUST NOT use the positional form. This is stage (1) only: no runtime deprecation warning, nothing removed. B2 — a NUL byte was silently truncating the body and still exiting 0, so "verbatim" only held for NUL-free input. `read -d ''` exits 0 only when it found its delimiter and non-zero at EOF, so that status now distinguishes a NUL-bearing input from a complete read, and such input is refused with nothing stored. The check runs before the empty-input check because a leading NUL yields an empty value AND a zero exit. Also: the "-- keeps a body that is literally --force working, and --force after it still applies" test never passed a trailing --force. It is split into a narrowed test plus a pair that sends to an unregistered recipient (refused unless force mode is on) with a control asserting the same call without the trailing flag fails on the roster check specifically. Examples use the delimiter AGMSG_BODY, not EOF: a body containing a bare EOF line would end the heredoc early and run the rest as shell commands. Co-Authored-By: Claude Opus 5 --- README.ja.md | 19 ++++- README.md | 19 +++-- SKILL.md | 29 ++++---- docs/building-on-agmsg.md | 2 +- scripts/drivers/types/antigravity/template.md | 18 ++++- scripts/drivers/types/claude-code/template.md | 18 ++++- scripts/drivers/types/codex/codex-bridge.js | 15 +++- scripts/drivers/types/codex/template.md | 18 ++++- scripts/drivers/types/copilot/template.md | 18 ++++- scripts/drivers/types/cursor/template.md | 18 ++++- scripts/drivers/types/gemini/template.md | 18 ++++- scripts/drivers/types/grok-build/_delivery.sh | 20 +++++- scripts/drivers/types/grok-build/template.md | 18 ++++- scripts/drivers/types/hermes/template.md | 18 ++++- scripts/drivers/types/opencode/template.md | 18 ++++- scripts/send.sh | 49 ++++++++----- scripts/session-start.sh | 10 ++- tests/test_messaging.bats | 70 ++++++++++++++++++- 18 files changed, 322 insertions(+), 73 deletions(-) diff --git a/README.ja.md b/README.ja.md index 95966c68..eb88593a 100644 --- a/README.ja.md +++ b/README.ja.md @@ -315,7 +315,8 @@ $agmsg ### シェル(任意のエージェント) ```bash -~/.agents/skills//scripts/send.sh "" [--force] +~/.agents/skills//scripts/send.sh --stdin [--force] +~/.agents/skills//scripts/send.sh --body-file [--force] ~/.agents/skills//scripts/inbox.sh ~/.agents/skills//scripts/history.sh [agent_id] [limit] ~/.agents/skills//scripts/team.sh @@ -325,7 +326,19 @@ $agmsg ~/.agents/skills//scripts/reset.sh [agent_id] ``` -`send.sh` は4つの位置引数 ` ""` に加えて、末尾に任意で `--force` を取る。シェルが1つの引数として認識するようメッセージはクォートすること — クォートされていないスペース入りメッセージは誤って分割される。`from`・`to` はどちらも `` に事前登録済みである必要があり、未登録の名前は(登録済み一覧を添えて)エラーになる — 意図的な事前登録前送信をしたい場合のみ `--force` でこのチェックを迂回できる。 +`send.sh` は ` ` に続けてメッセージを取り、末尾に任意で `--force` を取る。`from`・`to` はどちらも `` に事前登録済みである必要があり、未登録の名前は(登録済み一覧を添えて)エラーになる — 意図的な事前登録前送信をしたい場合のみ `--force` でこのチェックを迂回できる。 + +メッセージは `--stdin` または `--body-file ` で渡す。どちらもファイルディスクリプタから本文をそのまま読むため、本文がシェルを通ることも argv に載ることもない。ヒアドキュメントの区切り子は、本文中に単独行として現れ得ないものを選ぶこと — 本文に `AGMSG_BODY` だけの行があるとそこでヒアドキュメントが終わり、残りがシェルコマンドとして実行されてしまう。それを排除できない場合は `--body-file` を使う: + +```bash +~/.agents/skills//scripts/send.sh myteam alice bob --stdin <<'AGMSG_BODY' +ここに書いた内容はバイト単位でそのまま届く — バッククォートも $(...) もクォートもそのまま +AGMSG_BODY + +~/.agents/skills//scripts/send.sh myteam alice bob --body-file ./message.txt +``` + +従来の位置引数形式 — `send.sh ""` — も引き続き動作するが、**非推奨**である(#378)。このメッセージは agmsg が受け取る前にシェルが解釈するため、本文中のバッククォートや `$(...)` がそこで評価されうる。さらに Windows では MSYS の argv 変換によって長い本文が 8186 バイトで無言のうちに切り詰められる。**エージェントが組み立てたメッセージでは使ってはならない**。本文がちょうど `--stdin`・`--body-file`・`--force` そのものである場合は、直前に `--` を置くこと: `send.sh -- --stdin` ## FAQ / 設計メモ @@ -406,7 +419,7 @@ DBとチーム設定は保持される。更新されるのはスクリプトと ```bash # 独立したストアに対して実行 -AGMSG_STORAGE_PATH=/tmp/agmsg-sandbox ./scripts/send.sh myteam alice bob "hi" +printf 'hi' | AGMSG_STORAGE_PATH=/tmp/agmsg-sandbox ./scripts/send.sh myteam alice bob --stdin ``` ### サンドボックス互換性(Claude Code) diff --git a/README.md b/README.md index 8c271cc2..75d07727 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,8 @@ See [docs/opencode.md](docs/opencode.md) for full setup instructions. ### Shell (any agent) ```bash -~/.agents/skills//scripts/send.sh "" [--force] +~/.agents/skills//scripts/send.sh --stdin [--force] +~/.agents/skills//scripts/send.sh --body-file [--force] ~/.agents/skills//scripts/inbox.sh ~/.agents/skills//scripts/history.sh [agent_id] [limit] ~/.agents/skills//scripts/team.sh @@ -339,9 +340,19 @@ See [docs/opencode.md](docs/opencode.md) for full setup instructions. ~/.agents/skills//scripts/reset.sh [agent_id] ``` -`send.sh` takes four positional arguments — ` ""` — plus an optional trailing `--force`. Quote the message so the shell sees it as one argument; an unquoted message with spaces will be misparsed. Both `from` and `to` must already be registered in ``; an unregistered name errors out (listing the currently registered names) instead of silently storing an undeliverable message. Pass `--force` to bypass this check for an intentional pre-registration send. +`send.sh` takes ` `, then the message, then an optional trailing `--force`. Both `from` and `to` must already be registered in ``; an unregistered name errors out (listing the currently registered names) instead of silently storing an undeliverable message. Pass `--force` to bypass this check for an intentional pre-registration send. -A positional message is parsed by your shell before agmsg sees it, so a body containing backticks, `$(...)`, or tricky quoting can be evaluated or mangled there. For those bodies, replace `""` with `--stdin` (feed it a heredoc) or `--body-file ` — both read the body verbatim, with no shell re-interpretation. +Give the message via `--stdin` or `--body-file `. Both read the body verbatim from a file descriptor, so it never passes through your shell and never becomes an argv entry. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. + +```bash +~/.agents/skills//scripts/send.sh myteam alice bob --stdin <<'AGMSG_BODY' +whatever you write here arrives byte-for-byte — backticks, $(...), quotes and all +AGMSG_BODY + +~/.agents/skills//scripts/send.sh myteam alice bob --body-file ./message.txt +``` + +The older positional form — `send.sh ""` — still works, but it is **deprecated** (#378). Your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes by MSYS's argv conversion. A message composed by an agent **must not** use it. If your body happens to be literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. ## FAQ / Design notes @@ -427,7 +438,7 @@ The message store path resolves as **`AGMSG_STORAGE_PATH` (env) > built-in defau ```bash # Run against an isolated store -AGMSG_STORAGE_PATH=/tmp/agmsg-sandbox ./scripts/send.sh myteam alice bob "hi" +printf 'hi' | AGMSG_STORAGE_PATH=/tmp/agmsg-sandbox ./scripts/send.sh myteam alice bob --stdin ``` ### Sandbox compatibility (Claude Code) diff --git a/SKILL.md b/SKILL.md index 6d88fee4..4fcfa42f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -7,7 +7,7 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/agmsg/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/agmsg/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). ## How to use @@ -58,18 +58,23 @@ Do NOT manually edit config files. Always use join.sh. If the name was recently # Check inbox (marks messages as read) — DEFAULT action ~/.agents/skills/agmsg/scripts/inbox.sh -# Send a message (from/to must already be registered in ; add --force to bypass) -~/.agents/skills/agmsg/scripts/send.sh "" [--force] - -# A positional message goes through YOUR shell before agmsg ever sees it — a -# quoted body containing backticks or $(...) can be silently evaluated or -# executed there (#378). If the message contains backticks, $, or quotes, -# prefer --stdin (heredoc) or --body-file instead of fighting the shell with -# escaping; a plain message with none of those is fine quoted normally. -~/.agents/skills/agmsg/scripts/send.sh --stdin <<'EOF' +# Send a message (from/to must already be registered in ; add --force to bypass). +# Pass the body via --stdin or --body-file. Both read it verbatim from a file +# descriptor, so it never goes through a shell and never becomes an argv entry. +# Pick a delimiter the body cannot contain on a line by itself — a body with a +# bare AGMSG_BODY line would end the heredoc early and the rest would run as +# shell commands. When you cannot rule that out, use --body-file. +~/.agents/skills/agmsg/scripts/send.sh --stdin [--force] <<'AGMSG_BODY' message body goes here, verbatim — backticks, $(...), quotes all pass through untouched -EOF -~/.agents/skills/agmsg/scripts/send.sh --body-file /path/to/body.txt +AGMSG_BODY +~/.agents/skills/agmsg/scripts/send.sh --body-file /path/to/body.txt [--force] + +# DEPRECATED — do NOT use for a message you compose yourself (#378). A positional +# message goes through YOUR shell before agmsg ever sees it, so backticks or +# $(...) in the body can be evaluated there, and on Windows a long body is +# silently truncated at 8186 bytes. It still works, for callers that predate the +# flags above; new sends use --stdin or --body-file. +~/.agents/skills/agmsg/scripts/send.sh "" [--force] # A body that IS one of the flag names still works — put `--` before it. ~/.agents/skills/agmsg/scripts/send.sh -- --stdin diff --git a/docs/building-on-agmsg.md b/docs/building-on-agmsg.md index f70581bc..1cc2cdd8 100644 --- a/docs/building-on-agmsg.md +++ b/docs/building-on-agmsg.md @@ -58,7 +58,7 @@ flows use: | Action | Script | |---|---| -| Send a message | `scripts/send.sh ` | +| Send a message | `scripts/send.sh --stdin` (body on stdin) or `--body-file ` | | Join a team / register an agent | `scripts/join.sh ` | | Rename an agent | `scripts/rename.sh ` | | Remove an agent from a team | `scripts/leave.sh ` | diff --git a/scripts/drivers/types/antigravity/template.md b/scripts/drivers/types/antigravity/template.md index 5d739460..775ffc43 100644 --- a/scripts/drivers/types/antigravity/template.md +++ b/scripts/drivers/types/antigravity/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -82,7 +84,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -93,7 +100,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/claude-code/template.md b/scripts/drivers/types/claude-code/template.md index 0c0dcb35..e29271f6 100644 --- a/scripts/drivers/types/claude-code/template.md +++ b/scripts/drivers/types/claude-code/template.md @@ -4,7 +4,9 @@ description: Agent messaging — check inbox, send messages, view history Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -119,7 +121,12 @@ The allowlist merges across scopes and takes effect immediately — no restart n 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -130,7 +137,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument starts with "actas" followed by an agent name (e.g. "actas alice"): 1. Parse the new role name. If none was given (e.g. bare "actas", or the user asks you to suggest one), run `~/.agents/skills/__SKILL_NAME__/scripts/team.sh ` for each TEAM to see the current roster. Look for a naming convention already in play (e.g. a shared base name with role/number suffixes like `aggie-cc1`/`aggie-cc2`, or names derived from the team name) and, when one exists, propose 2-3 unused names that extend it; otherwise propose 2-3 short, distinctive identity names (not a bare tool-type label). Either way, names must not collide with the roster. Ask the user to pick one or type their own before continuing. diff --git a/scripts/drivers/types/codex/codex-bridge.js b/scripts/drivers/types/codex/codex-bridge.js index 0b073cf6..19a58fe4 100755 --- a/scripts/drivers/types/codex/codex-bridge.js +++ b/scripts/drivers/types/codex/codex-bridge.js @@ -1259,6 +1259,17 @@ class CodexBridge { buildPrompt() { const inbox = path.join(SCRIPTS_DIR, "inbox.sh"); const send = path.join(SCRIPTS_DIR, "send.sh"); + // #378: show the agent the stdin path, not the positional one. A reply it + // composes must not travel as an argument — the shell it builds the command + // in parses that first, and MSYS truncates a long argv entry at 8186 bytes. + // The positional form still works but is deprecated. The delimiter is + // deliberately not EOF: a body containing a bare EOF line would end the + // heredoc early and run the rest as shell commands. + const replyWith = [ + `${send} ${this.identity.team} ${this.identity.name} --stdin <<'AGMSG_BODY'`, + "", + "AGMSG_BODY", + ]; if (this.opts.inlineInbox) { return [ `agmsg delivered the following unread messages for ${this.identity.team}/${this.identity.name}:`, @@ -1266,14 +1277,14 @@ class CodexBridge { this.inlineInboxText.trim(), "", "Continue the conversation in this Codex thread. If a reply to an agmsg sender is needed, send it with:", - `${send} ${this.identity.team} ${this.identity.name} `, + ...replyWith, ].join("\n"); } return [ `agmsg has unread messages for ${this.identity.team}/${this.identity.name}.`, `Run: ${inbox} ${this.identity.team} ${this.identity.name}`, "Read the messages and continue the conversation. If a reply is needed, send it with:", - `${send} ${this.identity.team} ${this.identity.name} `, + ...replyWith, ].join("\n"); } diff --git a/scripts/drivers/types/codex/template.md b/scripts/drivers/types/codex/template.md index 38728ca6..676269c2 100644 --- a/scripts/drivers/types/codex/template.md +++ b/scripts/drivers/types/codex/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Always single-quote the `bash -lc` payload; NEVER use a double-quoted wrapper with escaped inner quotes (e.g. `bash -lc "... \"$PWD\" ..."`) — PowerShell parses `\"` as a literal backslash that terminates the string, silently corrupting the command and dropping everything after it. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Always single-quote the `bash -lc` payload; NEVER use a double-quoted wrapper with escaped inner quotes (e.g. `bash -lc "... \"$PWD\" ..."`) — PowerShell parses `\"` as a literal backslash that terminates the string, silently corrupting the command and dropping everything after it. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -88,7 +90,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -99,7 +106,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/copilot/template.md b/scripts/drivers/types/copilot/template.md index 9df689df..1c00c568 100644 --- a/scripts/drivers/types/copilot/template.md +++ b/scripts/drivers/types/copilot/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -82,7 +84,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -93,7 +100,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/cursor/template.md b/scripts/drivers/types/cursor/template.md index 085764fa..17b9f22e 100644 --- a/scripts/drivers/types/cursor/template.md +++ b/scripts/drivers/types/cursor/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -82,7 +84,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -93,7 +100,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/gemini/template.md b/scripts/drivers/types/gemini/template.md index a007beba..472a0a14 100644 --- a/scripts/drivers/types/gemini/template.md +++ b/scripts/drivers/types/gemini/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -82,7 +84,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -93,7 +100,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/grok-build/_delivery.sh b/scripts/drivers/types/grok-build/_delivery.sh index 6a645176..25239908 100644 --- a/scripts/drivers/types/grok-build/_delivery.sh +++ b/scripts/drivers/types/grok-build/_delivery.sh @@ -75,6 +75,10 @@ EOF # shifting project/type/name one slot left and leaving the watcher # subscribed to nothing. `-` survives the re-eval as a real argument and # watch.sh folds it into its empty-session-id resolution path. + # + # The send.sh example below uses the delimiter AGMSG_BODY, not EOF: it must + # not terminate THIS heredoc early, and a reader's body containing a bare + # EOF line must not terminate theirs either. cat < "$rule_file" # agmsg — keep a real-time inbox watcher running @@ -113,8 +117,20 @@ it once: you started and relaunch via the \`monitor\` tool — otherwise no message will ever reach you. 4. Each notification line is one message: - \` | | -> | \`. React as they arrive; reply with - \`$SKILL_DIR/scripts/send.sh ""\`. + \` | | -> | \`. React as they arrive; reply by + passing the body on stdin. A message you compose MUST NOT be passed as a + positional argument — your shell would parse it first, so backticks or + \$(...) in it can be evaluated, and on Windows a long body is silently + truncated at 8186 bytes (#378). The positional form still works but is + deprecated. Pick a delimiter your body cannot contain on a line by + itself — a body with a bare AGMSG_BODY line would end the heredoc early and + the rest would run as shell commands. If unsure, use --body-file. + +\`\`\`bash +$SKILL_DIR/scripts/send.sh --stdin <<'AGMSG_BODY' + +AGMSG_BODY +\`\`\` Launch it only once per session — if a watcher is already streaming, do not start a second one. Stopping is via the \`kill_command_or_subagent\` tool on the diff --git a/scripts/drivers/types/grok-build/template.md b/scripts/drivers/types/grok-build/template.md index 508a8f53..3f6dce39 100644 --- a/scripts/drivers/types/grok-build/template.md +++ b/scripts/drivers/types/grok-build/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -102,7 +104,12 @@ Each output line is one message: ` | | -> | `. Reac 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -113,7 +120,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/hermes/template.md b/scripts/drivers/types/hermes/template.md index 2f33f4de..ae67fcca 100644 --- a/scripts/drivers/types/hermes/template.md +++ b/scripts/drivers/types/hermes/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Hermes Agent skill for agmsg cross-agent messaging. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -66,7 +68,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -77,7 +84,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/drivers/types/opencode/template.md b/scripts/drivers/types/opencode/template.md index a1093999..1caafd9a 100644 --- a/scripts/drivers/types/opencode/template.md +++ b/scripts/drivers/types/opencode/template.md @@ -5,7 +5,9 @@ description: Cross-agent messaging via SQLite. Send messages between Claude Code Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** -**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/send.sh myteam alice bob "hello"'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). +**Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). + +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity @@ -82,7 +84,12 @@ Four possible outputs: 1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` 2. Do NOT ask the user what to do — just run the inbox check. 3. If there are messages, read and respond appropriately. To reply: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "history": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` @@ -93,7 +100,12 @@ If argument is "team": If argument starts with "send" (e.g. "send misaki check the server"): 1. Parse target agent and message from the arguments 2. Determine which team the target agent belongs to, then run: - `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +```bash +~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT --stdin <<'AGMSG_BODY' + +AGMSG_BODY +``` If argument is "config": 1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` diff --git a/scripts/send.sh b/scripts/send.sh index a9f8433f..87fa024d 100755 --- a/scripts/send.sh +++ b/scripts/send.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -# Usage: send.sh [--force] -# or: send.sh --stdin [--force] +# Usage: send.sh --stdin [--force] # or: send.sh --body-file [--force] +# or: send.sh [--force] (deprecated) # # #378: a message body passed positionally goes through the SENDER's shell # before it ever reaches this script — an unescaped `$(...)` in a quoted @@ -12,12 +12,19 @@ set -euo pipefail # argv-conversion path (build_argv -> globify), which truncates silently at # exactly 8186 bytes (fixed MAXPATHLEN 8192 buffer in glob.cc). --stdin and # --body-file read the body verbatim from a file descriptor instead of -# argv, so neither hazard applies — no shell re-interpretation, no argv -# size limit. The positional form is unchanged and remains the default. +# argv, so a body sent that way meets neither hazard — no shell +# re-interpretation, no argv size limit. +# +# That makes --stdin/--body-file the canonical way to send; it does not +# remove the hazard, because the positional form still exists and still +# carries both. The positional form is DEPRECATED: it keeps working for now +# so no existing caller breaks, but a body composed by an agent must not use +# it. Retiring it is a later, breaking stage of #378 — this stage only moves +# every first-party example onto the safe path. -USAGE="Usage: send.sh [--force] - or: send.sh --stdin [--force] - or: send.sh --body-file [--force]" +USAGE="Usage: send.sh --stdin [--force] + or: send.sh --body-file [--force] + or: send.sh [--force] (deprecated, see #378)" TEAM="${1:?$USAGE}" FROM="${2:?Missing from agent}" @@ -112,16 +119,23 @@ fi # whitespace/newlines — deliberately: whatever bytes are on stdin or in the # file land in the message exactly as given, including any trailing # newline(s). If you don't want a trailing newline in the sent message, -# don't put one in the input. `read` returns non-zero at EOF even though it -# captured the data, so failure here is expected and ignored. +# don't put one in the input. # -# Caveat shared with every other bash variable in this script (not new -# here): a NUL byte terminates both `read -d ''` and a bash string, so -# anything after one is lost. That is a shell-wide limit, not something -# --stdin/--body-file could relax — the old positional path had -# the exact same ceiling (argv strings can't hold NUL either). +# The exit status is load-bearing, so it is checked rather than discarded: +# `read` exits 0 only when it actually found its -d delimiter — which here +# is NUL — and non-zero when it reached EOF without one. EOF is the normal, +# complete read. A zero exit therefore means exactly one thing: the input +# carries a NUL byte, and BODY already stops there, because a bash string +# cannot hold one. Storing that shortened value and exiting 0 would tell the +# caller the whole body was sent when it was not, so a NUL-bearing body is +# refused instead. (A positional cannot hit this: argv strings +# cannot carry NUL either, so such a body never reaches this script intact +# in the first place.) if [ "$MODE" = "stdin" ]; then - IFS= read -r -d '' BODY || true + if IFS= read -r -d '' BODY; then + echo "Error: --stdin input contains a NUL byte; a message body must be text, and everything from that byte on would be lost. Nothing was sent." >&2 + exit 1 + fi if [ -z "$BODY" ]; then echo "Error: --stdin was given but no data was read from standard input." >&2 exit 1 @@ -135,7 +149,10 @@ elif [ "$MODE" = "file" ]; then echo "Error: --body-file '$BODY_FILE' is not readable." >&2 exit 1 fi - IFS= read -r -d '' BODY < "$BODY_FILE" || true + if IFS= read -r -d '' BODY < "$BODY_FILE"; then + echo "Error: --body-file '$BODY_FILE' contains a NUL byte; a message body must be text, and everything from that byte on would be lost. Nothing was sent." >&2 + exit 1 + fi if [ -z "$BODY" ]; then echo "Error: --body-file '$BODY_FILE' is empty." >&2 exit 1 diff --git a/scripts/session-start.sh b/scripts/session-start.sh index 44ebdb7d..be0f4f8d 100755 --- a/scripts/session-start.sh +++ b/scripts/session-start.sh @@ -307,8 +307,10 @@ following parameters, before any other action in this session. The 4th argument restricts receive to \`$ROLE_NAME\` only and re-claims its actas exclusivity lock — a resume restores context but not runtime state, so this -re-establishes the role. Send with \`send.sh ... $ROLE_NAME ...\` (this session's -active FROM is \`$ROLE_NAME\`). Each output line is one message: +re-establishes the role. Send with \`send.sh $ROLE_NAME --stdin\` and +the body on stdin (this session's active FROM is \`$ROLE_NAME\`) — a body you +compose must not be passed as a positional argument, which your shell parses +first and Windows truncates at 8186 bytes (#378). Each output line is one message: \` | | | \`. React as they arrive. Note: On a /clear or --continue/--resume re-fire, you may shortly see a @@ -331,7 +333,9 @@ before any other action in this session. This streams incoming agmsg messages into the session in real time. Each output line is one message: \` | | | \`. -React to messages as they arrive; reply with \`send.sh\`. +React to messages as they arrive; reply with \`send.sh --stdin\` +and the body on stdin — a body you compose must not be passed as a positional +argument, which your shell parses first and Windows truncates at 8186 bytes (#378). Note: On a /clear or --continue/--resume re-fire, you may shortly see a "Monitor … stopped" notification for an earlier 'agmsg inbox stream' diff --git a/tests/test_messaging.bats b/tests/test_messaging.bats index 708f00fc..d9fb3e3c 100644 --- a/tests/test_messaging.bats +++ b/tests/test_messaging.bats @@ -112,8 +112,9 @@ teardown() { # # A positional body goes through the sender's shell (backticks / $(...) can # execute or vanish) and, on Windows, through MSYS's argv-conversion path -# (silent truncation at 8186 bytes). --stdin/--body-file bypass both by -# never touching argv. +# (silent truncation at 8186 bytes). A body sent via --stdin/--body-file +# meets neither, because it never touches argv. The positional form remains +# available (deprecated) and still carries both hazards. @test "send: -- keeps a body that is literally --stdin working (backwards compat)" { # Before --stdin/--body-file existed, "--stdin" was just an ordinary body. @@ -125,7 +126,7 @@ teardown() { [ "$stored" = "--stdin" ] } -@test "send: -- keeps a body that is literally --force working, and --force after it still applies" { +@test "send: -- keeps a body that is literally --force working" { run bash "$SCRIPTS/send.sh" testteam alice bob -- --force [ "$status" -eq 0 ] local stored @@ -133,6 +134,34 @@ teardown() { [ "$stored" = "--force" ] } +# The two tests below are a pair, and only mean something together: the +# second is the control for the first. `-- --force --force` sends the body +# "--force" to an UNREGISTERED team/recipient, which the roster check (#355) +# refuses unless force mode is on — so the send succeeding is what proves the +# trailing --force was still parsed as a flag after `--` consumed the body. +# Without the control, that success could just as well mean the roster check +# never ran for an unknown team, and the test would assert nothing. +@test "send: a --force after a '--' body still turns force mode on" { + run bash "$SCRIPTS/send.sh" brandnewteam ghost nobody -- --force --force + [ "$status" -eq 0 ] + [[ "$output" =~ "Sent to nobody" ]] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='nobody';") + [ "$stored" = "--force" ] +} + +@test "send: control — the same '--' body without a trailing --force is still roster-checked" { + run bash "$SCRIPTS/send.sh" brandnewteam ghost nobody -- --force + [ "$status" -ne 0 ] + # Assert WHICH failure this is. A bare status check would also pass on an + # argument-parsing error, and then the pair would only show that the extra + # argument turns failure into success — not that it turned force mode on. + [[ "$output" =~ "has no registered agents" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + @test "send: -- with no body after it is an error, not an empty message" { run bash "$SCRIPTS/send.sh" testteam alice bob -- [ "$status" -ne 0 ] @@ -265,6 +294,41 @@ teardown() { [ "$n" -eq 0 ] } +# A bash string cannot hold a NUL byte, so `IFS= read -r -d ''` stops at the +# first one. Storing the shortened value with a success exit would report a +# whole body sent when it was not, so the input is refused instead. Note the +# NUL must be written by printf's FORMAT string: passing it through an +# argument (printf '%s' "$var") cannot work, because argv cannot carry NUL +# either — a test written that way would silently exercise NUL-free input. +@test "send: rejects --stdin input containing a NUL byte instead of truncating it" { + run bash -c "printf 'before\000after' | bash \"\$1\" testteam alice bob --stdin" _ "$SCRIPTS/send.sh" + [ "$status" -ne 0 ] + [[ "$output" =~ "NUL byte" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +@test "send: rejects a --body-file containing a NUL byte instead of truncating it" { + local f="$TEST_SKILL_DIR/nul.bin" + printf 'before\000after' > "$f" + run bash "$SCRIPTS/send.sh" testteam alice bob --body-file "$f" + [ "$status" -ne 0 ] + [[ "$output" =~ "NUL byte" ]] + local n + n=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT COUNT(*) FROM messages;") + [ "$n" -eq 0 ] +} + +# A leading NUL leaves the read with an empty value AND a zero exit, so the +# NUL check has to come before the "no data" check — otherwise this input +# would be blamed on an empty stdin rather than on the byte that caused it. +@test "send: reports a leading NUL as a NUL, not as empty input" { + run bash -c "printf '\000trailing' | bash \"\$1\" testteam alice bob --stdin" _ "$SCRIPTS/send.sh" + [ "$status" -ne 0 ] + [[ "$output" =~ "NUL byte" ]] +} + @test "send: rejects an unexpected extra argument after the message" { run bash "$SCRIPTS/send.sh" testteam alice bob "hello" extra [ "$status" -ne 0 ] From dcdbcb88eed9c674c4545e5ec96e876020b08d1a Mon Sep 17 00:00:00 2001 From: masashiono0611 Date: Wed, 29 Jul 2026 19:12:53 +0900 Subject: [PATCH 3/4] review: complete B1 on the runtime prompt, and correct the break to four bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second review pass (agmsg peer + self-audit) found three description/reality gaps of the same kind B3 was about. - The MUST NOT was missing from the codex bridge's runtime prompt — the one text an agent reads before composing a reply — where it existed only as a source comment. Emit it there. Also add the "deprecated" marking to docs/building-on-agmsg.md and both session-start.sh directives, which had the canonical form but not the wording B1 asks for in the same places. - The compat break is four literal bodies, not three: `--` itself was an ordinary positional body before (only argument 5 was inspected) and is now consumed as the terminator. Migration form is `-- --`. Pinned with a test. - send.sh's header claimed the positional form "keeps working for now so no existing caller breaks", contradicting the break documented alongside it. Co-Authored-By: Claude Opus 5 --- docs/building-on-agmsg.md | 5 +++++ scripts/drivers/types/codex/codex-bridge.js | 1 + scripts/send.sh | 15 +++++++++++---- scripts/session-start.sh | 9 +++++---- tests/test_messaging.bats | 12 ++++++++++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/docs/building-on-agmsg.md b/docs/building-on-agmsg.md index 1cc2cdd8..6de78b6d 100644 --- a/docs/building-on-agmsg.md +++ b/docs/building-on-agmsg.md @@ -65,6 +65,11 @@ flows use: | Check delivery mode | `scripts/delivery.sh status [ ]` | | Set delivery mode | `scripts/delivery.sh set ` | +`send.sh` also still accepts the message as a positional argument, but that +form is **deprecated** (#378): the caller's shell parses the body before +agmsg sees it, and on Windows MSYS truncates a long one at 8186 bytes. A body +your integration composes MUST NOT use it — pass it on stdin or from a file. + These are the same scripts `/agmsg` (or your configured command) itself shells out to — there is no more-privileged internal path. Treat them as the only sanctioned way to mutate agmsg state; nothing outside `scripts/` should diff --git a/scripts/drivers/types/codex/codex-bridge.js b/scripts/drivers/types/codex/codex-bridge.js index 19a58fe4..a0b995ab 100755 --- a/scripts/drivers/types/codex/codex-bridge.js +++ b/scripts/drivers/types/codex/codex-bridge.js @@ -1269,6 +1269,7 @@ class CodexBridge { `${send} ${this.identity.team} ${this.identity.name} --stdin <<'AGMSG_BODY'`, "", "AGMSG_BODY", + "A body you compose MUST NOT be passed as a positional argument (deprecated, #378); pick a delimiter it cannot contain on a line by itself.", ]; if (this.opts.inlineInbox) { return [ diff --git a/scripts/send.sh b/scripts/send.sh index 87fa024d..47446d2d 100755 --- a/scripts/send.sh +++ b/scripts/send.sh @@ -17,10 +17,17 @@ set -euo pipefail # # That makes --stdin/--body-file the canonical way to send; it does not # remove the hazard, because the positional form still exists and still -# carries both. The positional form is DEPRECATED: it keeps working for now -# so no existing caller breaks, but a body composed by an agent must not use -# it. Retiring it is a later, breaking stage of #378 — this stage only moves -# every first-party example onto the safe path. +# carries both. The positional form is DEPRECATED: it keeps working for now, +# but a body composed by an agent must not use it. Retiring it is a later, +# breaking stage of #378 — this stage only moves every first-party example +# onto the safe path. +# +# Four literal bodies DO break here, and `--` is their migration syntax: +# `--`, `--stdin` and `--body-file` are now consumed as a terminator or a +# mode selector, and `--force` is now rejected outright (it used to arrive as +# the body, because only argument 5 was checked for the flag). Send any of +# them as `send.sh -- ` — including `-- --`. Every +# other positional body is unaffected. USAGE="Usage: send.sh --stdin [--force] or: send.sh --body-file [--force] diff --git a/scripts/session-start.sh b/scripts/session-start.sh index be0f4f8d..bf9fcfac 100755 --- a/scripts/session-start.sh +++ b/scripts/session-start.sh @@ -309,8 +309,8 @@ The 4th argument restricts receive to \`$ROLE_NAME\` only and re-claims its acta exclusivity lock — a resume restores context but not runtime state, so this re-establishes the role. Send with \`send.sh $ROLE_NAME --stdin\` and the body on stdin (this session's active FROM is \`$ROLE_NAME\`) — a body you -compose must not be passed as a positional argument, which your shell parses -first and Windows truncates at 8186 bytes (#378). Each output line is one message: +compose must not be passed as the deprecated positional argument, which your +shell parses first and Windows truncates at 8186 bytes (#378). Each output line is one message: \` | | | \`. React as they arrive. Note: On a /clear or --continue/--resume re-fire, you may shortly see a @@ -334,8 +334,9 @@ before any other action in this session. This streams incoming agmsg messages into the session in real time. Each output line is one message: \` | | | \`. React to messages as they arrive; reply with \`send.sh --stdin\` -and the body on stdin — a body you compose must not be passed as a positional -argument, which your shell parses first and Windows truncates at 8186 bytes (#378). +and the body on stdin — a body you compose must not be passed as the deprecated +positional argument, which your shell parses first and Windows truncates at +8186 bytes (#378). Note: On a /clear or --continue/--resume re-fire, you may shortly see a "Monitor … stopped" notification for an earlier 'agmsg inbox stream' diff --git a/tests/test_messaging.bats b/tests/test_messaging.bats index d9fb3e3c..a11c3747 100644 --- a/tests/test_messaging.bats +++ b/tests/test_messaging.bats @@ -162,6 +162,18 @@ teardown() { [ "$n" -eq 0 ] } +# `--` itself was a valid positional body before the flags existed (only +# argument 5 was inspected, so argument 4 was taken verbatim). It is now +# consumed as the terminator, which is the fourth and least obvious of the +# literal bodies this change breaks — `-- --` is its migration form. +@test "send: -- keeps a body that is literally -- working (backwards compat)" { + run bash "$SCRIPTS/send.sh" testteam alice bob -- -- + [ "$status" -eq 0 ] + local stored + stored=$(sqlite3 "$TEST_SKILL_DIR/db/messages.db" "SELECT body FROM messages WHERE to_agent='bob';") + [ "$stored" = "--" ] +} + @test "send: -- with no body after it is an error, not an empty message" { run bash "$SCRIPTS/send.sh" testteam alice bob -- [ "$status" -ne 0 ] From 6e11992abe568e0bd6844f78c8ec98a86f543aba Mon Sep 17 00:00:00 2001 From: masashiono0611 Date: Wed, 29 Jul 2026 19:20:03 +0900 Subject: [PATCH 4/4] docs: carry the literal `--` case into the user-facing compat instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-through on the fourth affected literal body. The compat escape hatch is described in the README (both languages), SKILL.md and all nine driver templates, and each still listed only `--stdin` / `--body-file` / `--force` — the three cases known before `--` itself turned out to be affected. Those are exactly the places a caller looks when their body is one of these, so they now list all four and name `-- --` as the form for a body of `--`. Docs only; no behaviour change. The `--` case itself is already implemented and pinned by a test. Co-Authored-By: Claude Opus 5 --- README.ja.md | 2 +- README.md | 2 +- SKILL.md | 3 ++- scripts/drivers/types/antigravity/template.md | 2 +- scripts/drivers/types/claude-code/template.md | 2 +- scripts/drivers/types/codex/template.md | 2 +- scripts/drivers/types/copilot/template.md | 2 +- scripts/drivers/types/cursor/template.md | 2 +- scripts/drivers/types/gemini/template.md | 2 +- scripts/drivers/types/grok-build/template.md | 2 +- scripts/drivers/types/hermes/template.md | 2 +- scripts/drivers/types/opencode/template.md | 2 +- 12 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.ja.md b/README.ja.md index eb88593a..a0847dad 100644 --- a/README.ja.md +++ b/README.ja.md @@ -338,7 +338,7 @@ AGMSG_BODY ~/.agents/skills//scripts/send.sh myteam alice bob --body-file ./message.txt ``` -従来の位置引数形式 — `send.sh ""` — も引き続き動作するが、**非推奨**である(#378)。このメッセージは agmsg が受け取る前にシェルが解釈するため、本文中のバッククォートや `$(...)` がそこで評価されうる。さらに Windows では MSYS の argv 変換によって長い本文が 8186 バイトで無言のうちに切り詰められる。**エージェントが組み立てたメッセージでは使ってはならない**。本文がちょうど `--stdin`・`--body-file`・`--force` そのものである場合は、直前に `--` を置くこと: `send.sh -- --stdin` +従来の位置引数形式 — `send.sh ""` — も引き続き動作するが、**非推奨**である(#378)。このメッセージは agmsg が受け取る前にシェルが解釈するため、本文中のバッククォートや `$(...)` がそこで評価されうる。さらに Windows では MSYS の argv 変換によって長い本文が 8186 バイトで無言のうちに切り詰められる。**エージェントが組み立てたメッセージでは使ってはならない**。本文がちょうど `--`・`--stdin`・`--body-file`・`--force` そのものである場合は、直前に `--` を置くこと: `send.sh -- --stdin`(本文が `--` なら `-- --`) ## FAQ / 設計メモ diff --git a/README.md b/README.md index 75d07727..a3b8218e 100644 --- a/README.md +++ b/README.md @@ -352,7 +352,7 @@ AGMSG_BODY ~/.agents/skills//scripts/send.sh myteam alice bob --body-file ./message.txt ``` -The older positional form — `send.sh ""` — still works, but it is **deprecated** (#378). Your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes by MSYS's argv conversion. A message composed by an agent **must not** use it. If your body happens to be literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. +The older positional form — `send.sh ""` — still works, but it is **deprecated** (#378). Your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes by MSYS's argv conversion. A message composed by an agent **must not** use it. If your body happens to be literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. ## FAQ / Design notes diff --git a/SKILL.md b/SKILL.md index 4fcfa42f..4706d186 100644 --- a/SKILL.md +++ b/SKILL.md @@ -76,8 +76,9 @@ AGMSG_BODY # flags above; new sends use --stdin or --body-file. ~/.agents/skills/agmsg/scripts/send.sh "" [--force] -# A body that IS one of the flag names still works — put `--` before it. +# A body that IS `--` or one of the flag names still works — put `--` before it. ~/.agents/skills/agmsg/scripts/send.sh -- --stdin +~/.agents/skills/agmsg/scripts/send.sh -- -- # Message history ~/.agents/skills/agmsg/scripts/history.sh [agent_id] [limit] diff --git a/scripts/drivers/types/antigravity/template.md b/scripts/drivers/types/antigravity/template.md index 775ffc43..1727aed9 100644 --- a/scripts/drivers/types/antigravity/template.md +++ b/scripts/drivers/types/antigravity/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/claude-code/template.md b/scripts/drivers/types/claude-code/template.md index e29271f6..eec8ef1c 100644 --- a/scripts/drivers/types/claude-code/template.md +++ b/scripts/drivers/types/claude-code/template.md @@ -6,7 +6,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/codex/template.md b/scripts/drivers/types/codex/template.md index 676269c2..6c30cdd7 100644 --- a/scripts/drivers/types/codex/template.md +++ b/scripts/drivers/types/codex/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Always single-quote the `bash -lc` payload; NEVER use a double-quoted wrapper with escaped inner quotes (e.g. `bash -lc "... \"$PWD\" ..."`) — PowerShell parses `\"` as a literal backslash that terminates the string, silently corrupting the command and dropping everything after it. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/copilot/template.md b/scripts/drivers/types/copilot/template.md index 1c00c568..c4d05768 100644 --- a/scripts/drivers/types/copilot/template.md +++ b/scripts/drivers/types/copilot/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/cursor/template.md b/scripts/drivers/types/cursor/template.md index 17b9f22e..8db5bd2c 100644 --- a/scripts/drivers/types/cursor/template.md +++ b/scripts/drivers/types/cursor/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/gemini/template.md b/scripts/drivers/types/gemini/template.md index 472a0a14..b7c3b9f1 100644 --- a/scripts/drivers/types/gemini/template.md +++ b/scripts/drivers/types/gemini/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/grok-build/template.md b/scripts/drivers/types/grok-build/template.md index 3f6dce39..802154b7 100644 --- a/scripts/drivers/types/grok-build/template.md +++ b/scripts/drivers/types/grok-build/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/hermes/template.md b/scripts/drivers/types/hermes/template.md index ae67fcca..de3f8cf5 100644 --- a/scripts/drivers/types/hermes/template.md +++ b/scripts/drivers/types/hermes/template.md @@ -7,7 +7,7 @@ Hermes Agent skill for agmsg cross-agent messaging. **IMPORTANT: Always use the **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity diff --git a/scripts/drivers/types/opencode/template.md b/scripts/drivers/types/opencode/template.md index 1caafd9a..7f0d265a 100644 --- a/scripts/drivers/types/opencode/template.md +++ b/scripts/drivers/types/opencode/template.md @@ -7,7 +7,7 @@ Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER dir **Shell requirement:** All agmsg scripts are Bash scripts. Always execute them via `bash`, never via PowerShell or cmd directly. If your default shell is not Bash (e.g. PowerShell on Windows), wrap every command with `bash -lc '...'`. Example: `bash -lc '~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh myteam alice'`. Do NOT construct DB paths manually — the scripts handle path resolution internally. If you need to redirect storage, use `AGMSG_STORAGE_PATH` (the supported override). -**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. +**Sending a message:** pass the body on stdin with `--stdin`, or from a file with `--body-file `. Both read it verbatim from a file descriptor, so nothing you write is re-parsed by a shell or capped by an argv limit. The older positional form — `send.sh ""` — is **deprecated** (#378): your shell parses that message before agmsg ever sees it, so backticks or `$(...)` in the body can be evaluated there, and on Windows a long body is silently truncated at 8186 bytes. **A message you compose MUST NOT use the positional form.** If the body is literally `--`, `--stdin`, `--body-file`, or `--force`, put `--` in front of it: `send.sh -- --stdin` — and for a body of `--`, that is `-- --`. Pick a heredoc delimiter the body cannot contain on a line by itself — a body with a bare `AGMSG_BODY` line would end the heredoc early and the rest would run as shell commands. When you cannot rule that out, use `--body-file`. ## Identity