fix: binary download retry + prominent failure warning v1.7.79

- downloadBinaryFromRelease: 3 retries + 60s AbortController timeout
- Binary failure downgraded to warning (commands + skills still install)
- init.ts: red box warning + manual download URL + placement path on failure
- update.ts: backup binary before uninstall, restore on failure
- update.ts: subprocess timeout 120s → 300s (slow networks / China GFW)
- installer.test.ts: contextweaver E2E timeout 5s → 30s
This commit is contained in:
li committed 2026-03-11 20:58:04 +08:00
1 parent 7c5609aea6
commit 052cbb63c7
9 files changed
+147 -28

No files matched your search

+12
View File
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [1.7.79] - 2026-03-11
### 🐛 修复
- **Binary 下载容错**:`downloadBinaryFromRelease()` 新增 3 次重试 + 60s/次 AbortController 超时,适应中国用户 GitHub Release CDN 慢/不可达的场景
- **Binary 下载失败醒目告警**:下载失败时在终端显示红框警告 + 手动下载地址 + 放置路径 + chmod 指引,不再静默吞掉错误
- **Binary 失败不阻塞安装**:binary 下载失败降级为 warning,commands + skills 仍正常安装,不再将整个安装标记为失败
- **Update binary 备份/恢复**:更新流程先备份旧 binary,新安装失败后自动恢复,避免"删了旧的、新的又没下成"
- **Update subprocess 超时**:`npx init` 子进程超时从 120s 提升到 300s(5 分钟),避免 binary 下载慢时进程被杀导致 commands/skills 丢失
---
## [1.7.78] - 2026-03-11
### 🐛 修复
+5 -1
View File
@@ -2,7 +2,7 @@
> [根目录](../CLAUDE.md) > **skills-v2**
**Last Updated**: 2026-03-11 (v1.7.78)
**Last Updated**: 2026-03-11 (v1.7.79)
---
@@ -10,6 +10,10 @@
> 完整变更历史请查看 [CHANGELOG.md](./CHANGELOG.md)
### 2026-03-11 (v1.7.79)
- 🐛 **Binary 下载容错**:3 次重试 + 60s 超时 + 失败醒目告警(红框 + 手动修复指引)+ 不阻塞安装
- 🐛 **Update 流程加固**:binary 备份/恢复 + subprocess 超时 120s→300s
### 2026-03-11 (v1.7.78)
- 🐛 **Windows Hook exit 255 修复**:Windows 自动授权改用 `permissions.allow`,不再依赖 jq/grep
+1 -1
View File
@@ -352,4 +352,4 @@ MIT
---
v1.7.78 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [Contributing](./CONTRIBUTING.md)
v1.7.79 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [Contributing](./CONTRIBUTING.md)
+1 -1
View File
@@ -352,4 +352,4 @@ MIT
---
v1.7.78 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [参与贡献](./CONTRIBUTING.md)
v1.7.79 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues) | [参与贡献](./CONTRIBUTING.md)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ccg-workflow",
"version": "1.7.78",
"version": "1.7.79",
"description": "Claude + Codex + Gemini multi-model collaboration system - smart routing development workflow",
"type": "module",
"packageManager": "pnpm@10.17.1",
+40
View File
@@ -885,6 +885,46 @@ export async function init(options: InitOptions = {}): Promise<void> {
}
}
}
else {
// Binary download failed — show prominent warning with manual fix instructions
const binDest = join(installDir, 'bin')
const binaryExt = process.platform === 'win32' ? '.exe' : ''
const platformLabel = process.platform === 'darwin'
? (process.arch === 'arm64' ? 'darwin-arm64' : 'darwin-amd64')
: process.platform === 'linux'
? (process.arch === 'arm64' ? 'linux-arm64' : 'linux-amd64')
: (process.arch === 'arm64' ? 'windows-arm64' : 'windows-amd64')
const binaryFileName = `codeagent-wrapper-${platformLabel}${binaryExt}`
const destFileName = `codeagent-wrapper${binaryExt}`
const releaseUrl = `https://github.com/fengshao1227/ccg-workflow/releases/tag/preset`
console.log()
console.log(ansis.red.bold(` ╔════════════════════════════════════════════════════════════╗`))
console.log(ansis.red.bold(` ║ ⚠ codeagent-wrapper 下载失败 ║`))
console.log(ansis.red.bold(` ║ Binary download failed (network issue) ║`))
console.log(ansis.red.bold(` ╚════════════════════════════════════════════════════════════╝`))
console.log()
console.log(ansis.yellow(` 多模型协作命令 (/ccg:workflow, /ccg:plan 等) 需要此文件才能工作。`))
console.log(ansis.yellow(` Multi-model commands require this binary to work.`))
console.log()
console.log(ansis.cyan(` 手动修复 / Manual fix:`))
console.log()
console.log(ansis.white(` 1. 下载 / Download:`))
console.log(ansis.cyan(` ${releaseUrl}`))
console.log(ansis.gray(` → 找到 ${ansis.white(binaryFileName)} 并下载`))
console.log()
console.log(ansis.white(` 2. 放到 / Place at:`))
console.log(ansis.cyan(` ${binDest}/${destFileName}`))
console.log()
if (process.platform !== 'win32') {
console.log(ansis.white(` 3. 加权限 / Make executable:`))
console.log(ansis.cyan(` chmod +x "${binDest}/${destFileName}"`))
console.log()
}
console.log(ansis.white(` 或重新安装 / Or re-install:`))
console.log(ansis.cyan(` npx ccg-workflow@latest`))
console.log()
}
// Show MCP resources if user skipped installation
if (mcpProvider === 'skip' || ((mcpProvider === 'ace-tool' || mcpProvider === 'ace-tool-rs') && !aceToolToken) || (mcpProvider === 'contextweaver' && !contextWeaverApiKey)) {
+38 -3
View File
@@ -298,11 +298,24 @@ async function performUpdate(fromVersion: string, toVersion: string, isNewVersio
}
}
// Step 3: Delete old workflows first
// Step 3: Backup binary + Delete old workflows
spinner = ora(i18n.t('update:removingOld')).start()
const installDir = join(homedir(), '.claude')
const binDir = join(installDir, 'bin')
const wrapperName = process.platform === 'win32' ? 'codeagent-wrapper.exe' : 'codeagent-wrapper'
const wrapperPath = join(binDir, wrapperName)
const wrapperBackup = join(binDir, `${wrapperName}.bak`)
let binaryBackedUp = false
try {
const installDir = join(homedir(), '.claude')
// Backup existing binary before uninstall (restore if new install fails)
const fsExtra = await import('fs-extra')
if (await fsExtra.pathExists(wrapperPath)) {
await fsExtra.copy(wrapperPath, wrapperBackup)
binaryBackedUp = true
}
const uninstallResult = await uninstallWorkflows(installDir)
if (uninstallResult.success) {
@@ -324,7 +337,7 @@ async function performUpdate(fromVersion: string, toVersion: string, isNewVersio
try {
await execAsync(`npx --yes ccg-workflow@latest init --force --skip-mcp --skip-prompt`, {
timeout: 120000,
timeout: 300000, // 5min — binary download from GitHub Release may be slow (especially in China)
env: {
...process.env,
CCG_UPDATE_MODE: 'true',
@@ -332,6 +345,12 @@ async function performUpdate(fromVersion: string, toVersion: string, isNewVersio
})
spinner.succeed(i18n.t('update:installDone'))
// Clean up binary backup on success
if (binaryBackedUp) {
const fsExtra = await import('fs-extra')
await fsExtra.remove(wrapperBackup).catch(() => {})
}
// Read updated config to display installed commands
const config = await readCcgConfig()
if (config?.workflows?.installed) {
@@ -344,6 +363,22 @@ async function performUpdate(fromVersion: string, toVersion: string, isNewVersio
}
catch (error) {
spinner.fail(i18n.t('update:installFailed'))
// Restore backed-up binary if new install failed
if (binaryBackedUp) {
try {
const fsExtra = await import('fs-extra')
if (await fsExtra.pathExists(wrapperBackup)) {
await fsExtra.ensureDir(binDir)
await fsExtra.move(wrapperBackup, wrapperPath, { overwrite: true })
console.log(ansis.yellow(` • codeagent-wrapper restored from backup`))
}
}
catch {
// Backup restore failed — nothing more we can do
}
}
console.log(ansis.red(`${i18n.t('common:error')}: ${error}`))
console.log()
console.log(ansis.yellow(i18n.t('update:manualRetry')))
+1 -1
View File
@@ -226,7 +226,7 @@ describe('installWorkflows E2E — mcpProvider="contextweaver"', () => {
})
expect(result.success).toBe(true)
expect(result.errors).toEqual([])
})
}, 30_000)
it('generated command files contain contextweaver references', async () => {
const planContent = readFileSync(join(tmpDir, 'commands', 'ccg', 'plan.md'), 'utf-8')
+48 -20
View File
@@ -33,25 +33,53 @@ const PACKAGE_ROOT = findPackageRoot(__dirname)
* Download codeagent-wrapper binary from GitHub Release.
* Uses the fixed `preset` tag release for pre-compiled binaries.
* Supports redirect-following (GitHub releases redirect to CDN).
*
* Retry: 3 attempts with exponential backoff (2s, 4s, 6s).
* Timeout: 60s per attempt via AbortController.
*/
async function downloadBinaryFromRelease(binaryName: string, destPath: string): Promise<boolean> {
const url = `${BINARY_DOWNLOAD_URL}/${binaryName}`
const MAX_ATTEMPTS = 3
const TIMEOUT_MS = 60_000 // 60s per attempt
// Use dynamic import for https to follow redirects manually (Node.js fetch follows automatically)
const response = await fetch(url, { redirect: 'follow' })
if (!response.ok) {
return false
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)
const response = await fetch(url, { redirect: 'follow', signal: controller.signal })
if (!response.ok) {
clearTimeout(timer)
if (attempt < MAX_ATTEMPTS) {
await new Promise(resolve => setTimeout(resolve, attempt * 2000))
continue
}
return false
}
const buffer = Buffer.from(await response.arrayBuffer())
clearTimeout(timer)
await fs.writeFile(destPath, buffer)
// Set executable permission on Unix-like systems
if (process.platform !== 'win32') {
await fs.chmod(destPath, 0o755)
}
return true
}
catch {
if (attempt < MAX_ATTEMPTS) {
// Exponential backoff before retry
await new Promise(resolve => setTimeout(resolve, attempt * 2000))
continue
}
return false
}
}
const buffer = Buffer.from(await response.arrayBuffer())
await fs.writeFile(destPath, buffer)
// Set executable permission on Unix-like systems
if (process.platform !== 'win32') {
await fs.chmod(destPath, 0o755)
}
return true
return false
}
// All available commands (20 total after adding spec commands)
@@ -845,7 +873,7 @@ ${workflow.description}
const destBinary = join(binDir, platform === 'win32' ? 'codeagent-wrapper.exe' : 'codeagent-wrapper')
// Download from GitHub Release (only source)
// Download from GitHub Release (3 attempts with retry)
const installed = await downloadBinaryFromRelease(binaryName, destBinary)
if (installed) {
@@ -857,18 +885,18 @@ ${workflow.description}
result.binInstalled = true
}
catch (verifyError) {
result.errors.push(`Binary verification failed: ${verifyError}`)
result.success = false
// Binary downloaded but verification failed — warning, not blocking
result.errors.push(`Binary verification failed (non-blocking): ${verifyError}`)
}
}
else {
result.errors.push(`Failed to download binary: ${binaryName} from GitHub Release. Check network or visit https://github.com/${GITHUB_REPO}/releases/tag/${RELEASE_TAG}`)
result.success = false
// Binary download failed — warning, not blocking (commands + skills still work)
result.errors.push(`Failed to download binary: ${binaryName} from GitHub Release (after 3 attempts). Check network or visit https://github.com/${GITHUB_REPO}/releases/tag/${RELEASE_TAG}`)
}
}
catch (error) {
result.errors.push(`Failed to install codeagent-wrapper: ${error}`)
result.success = false
// Binary install exception — warning, not blocking
result.errors.push(`Failed to install codeagent-wrapper (non-blocking): ${error}`)
}
result.configPath = commandsDir