Problem
在實際使用 idd-issue/idd-clarify 為一則含 LaTeX 數學公式的 issue(kiki830621/article1-polychoric-ordinal-sem-ifa-identifiability#1)做多輪 body 編輯時,發現 GitHub 上的 issue body 被靜默損壞:\Sigma_\nu、\equiv 這類 LaTeX 反斜線序列,被轉成真正的換行字元與 ESC (0x1B) 控制字元。不是 gh CLI 的問題,是 zsh 的 echo builtin 預設會解讀反斜線跳脫序列(等同 bash 的 echo -e,跟 bash 的 echo 預設行為不同)。
已在自己的 session 裡定位到根因並用 Python 重寫該則 issue 修復,但同一個寫法(echo "$VAR" > file 後續接 --body-file dispatch)在 idd-edit skill 的官方參考程式碼裡也有兩處一模一樣的寫法,任何 zsh 使用者只要編輯的內容含 LaTeX 或其他「反斜線+單一字母剛好是 C-style escape」的文字,都會踩到同一個坑。
Type
bug
根因(已用最小重現案例驗證,非猜測)
# 在 zsh 裡執行:
TEST_VAR='hello\nworld\eEND'
echo "$TEST_VAR" | od -c
# 結果:\n 被轉成真正的換行、\e 被轉成 ESC(033) 控制字元
# h e l l o \n(真換行) w o r l d ESC E N D
printf '%s' "$TEST_VAR" | od -c
# 結果:逐字保留,backslash n w o r l d backslash e E N D(正確)
zsh 的 echo builtin(不加任何 flag)預設就是 echo -e 語意;bash 的 echo 預設不是。這個 plugin 的使用者環境不保證是 bash——Bash 這個 harness tool 名稱容易讓人誤以為底層一定是 bash,但實際上很多使用者(包含我)的預設互動 shell 是 zsh。
受影響的確切位置(已逐一 grep 確認,全 repo 只有這兩處符合 echo "$VAR" > "$FILE" 這個 pattern)
skills/idd-edit/SKILL.md:
310: echo "$BODY_INPUT" > "$REPL_FILE"
376:echo "$NEW_BODY" > "$TMP_BODY_FILE"
第 310 行:$BODY_INPUT(使用者想替換進去的新內容)寫進 $REPL_FILE,接著餵給 idd-edit-helper.py section-replace 做 named-section 替換——如果 $BODY_INPUT 含 LaTeX,替換結果從這一步就已經壞了。
已延伸掃描其他可能變體(無空白分隔、未加雙引號、多變數內插等形式),確認沒有其他「把內容寫進檔案供後續 dispatch」的同類實例——其餘命中的 echo "...文字 $VAR..." >&2 全部是印給人看的 log/錯誤訊息,不是要寫檔或送出的內容,不構成同一類風險。
第 376 行更直接:緊接著在同一段程式碼裡就是
echo "$NEW_BODY" > "$TMP_BODY_FILE"
...
bash "$CLAUDE_PLUGIN_ROOT/scripts/gh-egress.sh" edit-comment "$COMMENT_ID" \
--repo "$REPO" --body-file "$TMP_BODY_FILE" --scrub-attested "$SCRUB_LEVEL"
這跟我在自己 session 裡踩到、重現的失敗模式完全同構:先把 body 內容寫進暫存檔,再用 --body-file 派送。在 zsh 下,$NEW_BODY 只要含 \n/\t/\b/\a/\f/\v/\e/\r 這幾種 2-字元序列(剛好對應到大量常見 LaTeX 巨集的字首:\nu、\tau、\beta、\alpha、\forall、\vee、\equiv、\rho 等),對應的兩個字元就會被解讀成真正的控制字元,靜默寫進 dispatch 的 body-file,gh-egress.sh 的既有防護網(--scrub-attested 語意隱私自審、mention 偵測等)完全不會攔這種機械性資料毀損,因為它們檢查的是內容語意,不是位元完整性。
為什麼不是 gh-egress.sh 或 gh CLI 的問題(已排除)
用完全相同的乾淨檔案分別測過:
- 原生
gh issue edit --body-file <乾淨檔>(不經過 wrapper)→ 結果正確
- 經過
gh-egress.sh edit --body-file <乾淨檔>(有 wrapper)→ 結果也正確
- 唯獨
echo "$VAR" > file(zsh)產生的檔案本身就已經壞了,餵給誰都一樣壞
gh-egress.sh 本身透過 "${GH_ARGS[@]}" 陣列展開傳遞參數,過程不涉及任何 echo/printf 重新序列化,是乾淨的。
Expected
任何把 body/comment 內容寫進暫存檔供後續 --body-file dispatch 的地方,都應該逐位元保留輸入,不因為內容剛好長得像某種 shell escape 序列而被改寫。
Actual
skills/idd-edit/SKILL.md 第 310、376 行用 echo "$VAR" > file,在 zsh 環境下對含特定反斜線序列的內容(LaTeX 是最常見的觸發源)造成靜默資料毀損:真實換行與 ESC 控制字元混入本該是純文字的 GitHub issue/comment body。
Impact
- 任何統計/數學/物理背景的使用者,只要編輯的 issue 或 comment 含 LaTeX 數學符號(
\nu、\beta、\alpha、\tau、\forall、\vee、\equiv 等以 n t b a f v e r 開頭的巨集),在 zsh 下用 idd-edit 的 named-section replace 或 comment 編輯路徑,都會靜默壞掉,且沒有任何錯誤訊息——gh-egress.sh 的隱私自審與機械 net 都不會攔這種位元層級的毀損。
- 已知受害案例:
kiki830621/article1-polychoric-ordinal-sem-ifa-identifiability#1(本人 session 內,已自行修復)。
- 由於是靜默失效,過去是否已有其他使用者在不知情的狀況下產生過壞掉的 issue body,無從得知——這正是這類 bug 最危險的地方。
建議修法(供 fix 時參考,非強制)
兩處 echo "$VAR" > "$FILE" 改成 printf '%s' "$VAR" > "$FILE"(printf %s 對參數值不做任何跳脫解讀,逐位元保留)即可修好這兩個已知位置。
但這是同一類問題在整個 plugin 裡的其中兩個實例,不代表窮盡了所有風險——建議把「shell script 裡任何把使用者/GitHub 內容寫進檔案或送進管線」的地方都盤點一次,確認一律用 printf '%s' 而不是 echo(printf 的 format string 用單引號固定字面 '%s',值本身一律當資料、不重新解析)。另外也可以在 IDD 通用開發規範裡加一條「shell script 一律假設在 zsh 下執行(Bash harness tool 的底層不保證是 bash),不可依賴 bash-only 的 echo 語意」——這類坑很可能還有其他實例沒被踩到。
Clarity Surface(idd-clarify run 2026-09-05T01:58:00Z)
| Type |
Source |
Question for you |
Status |
| (none) |
— |
no issues detected |
passed |
Problem
在實際使用
idd-issue/idd-clarify為一則含 LaTeX 數學公式的 issue(kiki830621/article1-polychoric-ordinal-sem-ifa-identifiability#1)做多輪 body 編輯時,發現 GitHub 上的 issue body 被靜默損壞:\Sigma_\nu、\equiv這類 LaTeX 反斜線序列,被轉成真正的換行字元與 ESC (0x1B) 控制字元。不是ghCLI 的問題,是 zsh 的echobuiltin 預設會解讀反斜線跳脫序列(等同 bash 的echo -e,跟 bash 的echo預設行為不同)。已在自己的 session 裡定位到根因並用 Python 重寫該則 issue 修復,但同一個寫法(
echo "$VAR" > file後續接--body-filedispatch)在idd-editskill 的官方參考程式碼裡也有兩處一模一樣的寫法,任何 zsh 使用者只要編輯的內容含 LaTeX 或其他「反斜線+單一字母剛好是 C-style escape」的文字,都會踩到同一個坑。Type
bug
根因(已用最小重現案例驗證,非猜測)
zsh 的
echobuiltin(不加任何 flag)預設就是echo -e語意;bash 的echo預設不是。這個 plugin 的使用者環境不保證是 bash——Bash這個 harness tool 名稱容易讓人誤以為底層一定是 bash,但實際上很多使用者(包含我)的預設互動 shell 是 zsh。受影響的確切位置(已逐一 grep 確認,全 repo 只有這兩處符合
echo "$VAR" > "$FILE"這個 pattern)skills/idd-edit/SKILL.md:第 310 行:
$BODY_INPUT(使用者想替換進去的新內容)寫進$REPL_FILE,接著餵給idd-edit-helper.py section-replace做 named-section 替換——如果$BODY_INPUT含 LaTeX,替換結果從這一步就已經壞了。已延伸掃描其他可能變體(無空白分隔、未加雙引號、多變數內插等形式),確認沒有其他「把內容寫進檔案供後續 dispatch」的同類實例——其餘命中的
echo "...文字 $VAR..." >&2全部是印給人看的 log/錯誤訊息,不是要寫檔或送出的內容,不構成同一類風險。第 376 行更直接:緊接著在同一段程式碼裡就是
這跟我在自己 session 裡踩到、重現的失敗模式完全同構:先把 body 內容寫進暫存檔,再用
--body-file派送。在 zsh 下,$NEW_BODY只要含\n/\t/\b/\a/\f/\v/\e/\r這幾種 2-字元序列(剛好對應到大量常見 LaTeX 巨集的字首:\nu、\tau、\beta、\alpha、\forall、\vee、\equiv、\rho等),對應的兩個字元就會被解讀成真正的控制字元,靜默寫進 dispatch 的 body-file,gh-egress.sh的既有防護網(--scrub-attested語意隱私自審、mention 偵測等)完全不會攔這種機械性資料毀損,因為它們檢查的是內容語意,不是位元完整性。為什麼不是
gh-egress.sh或ghCLI 的問題(已排除)用完全相同的乾淨檔案分別測過:
gh issue edit --body-file <乾淨檔>(不經過 wrapper)→ 結果正確gh-egress.sh edit --body-file <乾淨檔>(有 wrapper)→ 結果也正確echo "$VAR" > file(zsh)產生的檔案本身就已經壞了,餵給誰都一樣壞gh-egress.sh本身透過"${GH_ARGS[@]}"陣列展開傳遞參數,過程不涉及任何 echo/printf 重新序列化,是乾淨的。Expected
任何把 body/comment 內容寫進暫存檔供後續
--body-filedispatch 的地方,都應該逐位元保留輸入,不因為內容剛好長得像某種 shell escape 序列而被改寫。Actual
skills/idd-edit/SKILL.md第 310、376 行用echo "$VAR" > file,在 zsh 環境下對含特定反斜線序列的內容(LaTeX 是最常見的觸發源)造成靜默資料毀損:真實換行與 ESC 控制字元混入本該是純文字的 GitHub issue/comment body。Impact
\nu、\beta、\alpha、\tau、\forall、\vee、\equiv等以n t b a f v e r開頭的巨集),在 zsh 下用idd-edit的 named-section replace 或 comment 編輯路徑,都會靜默壞掉,且沒有任何錯誤訊息——gh-egress.sh的隱私自審與機械 net 都不會攔這種位元層級的毀損。kiki830621/article1-polychoric-ordinal-sem-ifa-identifiability#1(本人 session 內,已自行修復)。建議修法(供 fix 時參考,非強制)
兩處
echo "$VAR" > "$FILE"改成printf '%s' "$VAR" > "$FILE"(printf %s對參數值不做任何跳脫解讀,逐位元保留)即可修好這兩個已知位置。但這是同一類問題在整個 plugin 裡的其中兩個實例,不代表窮盡了所有風險——建議把「shell script 裡任何把使用者/GitHub 內容寫進檔案或送進管線」的地方都盤點一次,確認一律用
printf '%s'而不是echo(printf的 format string 用單引號固定字面'%s',值本身一律當資料、不重新解析)。另外也可以在 IDD 通用開發規範裡加一條「shell script 一律假設在 zsh 下執行(Bashharness tool 的底層不保證是 bash),不可依賴 bash-only 的echo語意」——這類坑很可能還有其他實例沒被踩到。Clarity Surface(idd-clarify run 2026-09-05T01:58:00Z)