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:
1 parent
7c5609aea6
commit
052cbb63c7
9 files changed
+147
-28
No files matched your search
@@ -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
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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')))
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user