From 5fad621130852144877cf01634b92dc33bd4264f Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 24 Jun 2026 01:18:41 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=80=9A=E7=9F=A5=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E6=94=B9=E5=BC=82=E6=AD=A5=E5=90=8E=E5=8F=B0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=EF=BC=8Chook=20=E4=B8=8D=E5=86=8D=E5=9B=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=85=A2=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - exe 缺失时派生脱离 hook 的后台下载进程,立即返回并通过 systemMessage 汇报进度,不受 30s 超时影响 - curl -C - 断点续传,失败保留 .partial 下次接着下 - 原子 mkdir 锁防止多次触发重复下载;进程被硬杀的陈旧锁靠 .partial 闲置自愈 - systemMessage 用 ASCII(中文经 cmd 会被破坏成非法 JSON) --- scripts/notify.cmd | 82 +++++++++++++++++++++++++++------------------- scripts/notify.sh | 55 +++++++++++++++++-------------- 2 files changed, 79 insertions(+), 58 deletions(-) diff --git a/scripts/notify.cmd b/scripts/notify.cmd index ad1d06a..246f394 100644 --- a/scripts/notify.cmd +++ b/scripts/notify.cmd @@ -6,40 +6,56 @@ rem ============================================================ setlocal set "BIN=%~dp0..\bin" set "EXE=%BIN%\notify.exe" +set "PART=%BIN%\notify.exe.partial" set "LOCK=%BIN%\notify.download.lock" -rem only bootstrap on first run; the common path runs the exe directly (keeps piped stdin intact) -if not exist "%EXE%" call :bootstrap +rem hidden self-reinvocation: detached background downloader (see :downloader) +if "%~1"=="__download" goto downloader -if exist "%EXE%" "%EXE%" %* -endlocal -exit /b - -:bootstrap -if not exist "%BIN%" mkdir "%BIN%" 2>nul -set "TMP=%BIN%\notify.exe.%RANDOM%.tmp" -rem mkdir is atomic; success = this process downloads, failure = someone else is downloading -mkdir "%LOCK%" 2>nul -if errorlevel 1 goto :waitdl -rem double-check in case it just finished -if exist "%EXE%" ( rmdir "%LOCK%" 2>nul & exit /b ) -rem download to temp then atomic rename, so no half-written exe is ever seen -curl -fsSL "%DOWNLOAD_URL%" -o "%TMP%" -if errorlevel 1 ( del "%TMP%" 2>nul & rmdir "%LOCK%" 2>nul & exit /b ) -move /y "%TMP%" "%EXE%" >nul -rmdir "%LOCK%" 2>nul -exit /b - -:waitdl -rem did not get the lock; wait for the exe to appear (up to ~60s) -set /a _w=0 -:waitloop -if exist "%EXE%" exit /b -if %_w% geq 120 ( - rem timed out; a killed download may have left a stale lock, clear it for next time - rmdir "%LOCK%" 2>nul - exit /b +rem common path: exe present -> run directly (keeps piped stdin intact) +if exist "%EXE%" ( + "%EXE%" %* + endlocal & exit /b ) -ping -n 2 127.0.0.1 >nul -set /a _w+=1 -goto :waitloop + +rem ---- exe missing: never block the hook ---- +rem kick off the download once (atomic mkdir lock); a detached worker survives the +rem hook timeout. then report progress to Claude and return immediately. +if not exist "%BIN%" mkdir "%BIN%" 2>nul + +rem self-heal: if a previous worker was hard-killed (shutdown/crash) it may leave a +rem stale lock that blocks all retries. reclaim it so -C - can resume the .partial. +if exist "%LOCK%" call :reclaim + +rem atomic lock: only the first hook spawns the worker; others fall through and just +rem report progress -> no duplicate downloads even when hooks fire concurrently. +mkdir "%LOCK%" 2>nul && start "" /b "%~f0" __download nul 2>&1 + +rem downloaded size so far (from the .partial file), shown as X.X MB +set "DLBYTES=0" +if exist "%PART%" for %%A in ("%PART%") do set "DLBYTES=%%~zA" +set /a DLKB=DLBYTES/1024 +set /a DLMB=DLKB/1024 +set /a DLF=(DLKB*10/1024)%%10 + +rem exit 0 so Claude parses this JSON; systemMessage is shown to the user, the +rem notification itself is skipped this time (suppressOutput hides raw stdout) +echo {"suppressOutput":true,"systemMessage":"[Claude Code Notify] notifier not ready, downloading in background: %DLMB%.%DLF% MB / ~14 MB done. Works automatically once finished; this notification is skipped."} +endlocal & exit /b 0 + +:downloader +rem detached worker: resume-capable download (-C -), atomic install, always free lock. +rem if curl fails (slow/flaky net), the .partial is kept and the next hook resumes it. +curl -fsSL -C - "%DOWNLOAD_URL%" -o "%PART%" +if not errorlevel 1 move /y "%PART%" "%EXE%" >nul 2>&1 +rmdir "%LOCK%" 2>nul +endlocal & exit /b + +:reclaim +rem no .partial yet => worker died before downloading anything; reclaim immediately +if not exist "%PART%" ( rmdir "%LOCK%" 2>nul & goto :eof ) +rem .partial idle for >120s => worker is dead (a live curl writes continuously); reclaim +for /f "delims=" %%T in ('powershell -nop -c "[int]((Get-Date)-(Get-Item '%PART%').LastWriteTime).TotalSeconds" 2^>nul') do set "IDLE=%%T" +if not defined IDLE goto :eof +if %IDLE% geq 120 rmdir "%LOCK%" 2>nul +goto :eof diff --git a/scripts/notify.sh b/scripts/notify.sh index 123ce6a..fd71037 100644 --- a/scripts/notify.sh +++ b/scripts/notify.sh @@ -7,34 +7,39 @@ DOWNLOAD_URL="https://git.pchuan.top/cc-tools/notify/releases/download/v1.0.0/no DIR="$(cd "$(dirname "$0")" && pwd)" BIN="$DIR/../bin" EXE="$BIN/notify.exe" +PART="$BIN/notify.exe.partial" LOCK="$BIN/notify.download.lock" -if [ ! -f "$EXE" ]; then - mkdir -p "$BIN" 2>/dev/null - # mkdir 是原子操作,用作锁:成功=本进程负责下载,失败=已有进程在下 - if mkdir "$LOCK" 2>/dev/null; then - # 双重检查,避免刚好别人下完 - if [ ! -f "$EXE" ]; then - TMP="$BIN/notify.exe.$$.tmp" - if curl -fsSL "$DOWNLOAD_URL" -o "$TMP"; then - mv -f "$TMP" "$EXE" # 原子改名,避免半截 exe - chmod +x "$EXE" 2>/dev/null - else - rm -f "$TMP" - fi - fi +# 常规路径:exe 已就绪 -> 直接转发参数与 stdin(保持管道完整) +if [ -f "$EXE" ]; then + exec "$EXE" "$@" +fi + +# ---- exe 缺失:绝不阻塞 hook ---- +mkdir -p "$BIN" 2>/dev/null + +# 自愈:上次下载进程被硬杀(关机/崩溃)可能留下陈旧锁,挡住所有重试。 +# .partial 不存在(没真正开始)或 >2 分钟没增长(进程已死)则回收锁,让 -C - 续传。 +if [ -d "$LOCK" ]; then + if [ ! -f "$PART" ]; then + rmdir "$LOCK" 2>/dev/null + elif [ -n "$(find "$PART" -mmin +2 2>/dev/null)" ]; then rmdir "$LOCK" 2>/dev/null - else - # 没抢到锁:等 exe 出现(最多约 60 秒) - i=0 - while [ ! -f "$EXE" ] && [ "$i" -lt 120 ]; do - sleep 0.5 - i=$((i + 1)) - done - # 超时仍没下好:可能上次下载被杀留下陈旧锁,清掉让下次重下 - [ ! -f "$EXE" ] && rmdir "$LOCK" 2>/dev/null fi fi -# 转发全部参数与 stdin 给真正的 exe -[ -f "$EXE" ] && exec "$EXE" "$@" +# 原子锁:只有第一个 hook 派生唯一的后台下载进程;并发/后续 hook 抢锁失败 +# -> 不重复下载,只汇报进度。下载脱离 hook 后台进行,不受 30s 超时影响。 +if mkdir "$LOCK" 2>/dev/null; then + # 断点续传 -C -;失败保留 .partial 供下次续传;无论成败都释放锁 + nohup sh -c "curl -fsSL -C - '$DOWNLOAD_URL' -o '$PART' && mv -f '$PART' '$EXE' && chmod +x '$EXE'; rmdir '$LOCK' 2>/dev/null" >/dev/null 2>&1 & +fi + +# 已下载大小(取 .partial 字节数),汇报给 Claude +DLBYTES=0 +[ -f "$PART" ] && DLBYTES=$(wc -c < "$PART" 2>/dev/null | tr -d ' ') +DLMB=$(awk "BEGIN{printf \"%.1f\", $DLBYTES/1048576}") + +# exit 0 让 Claude 解析此 JSON;systemMessage 展示给用户,本次通知跳过 +printf '{"suppressOutput":true,"systemMessage":"[Claude Code Notify] notifier not ready, downloading in background: %s MB / ~14 MB done. Works automatically once finished; this notification is skipped."}\n' "$DLMB" +exit 0