diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index f5cecc14..ec3fe81d 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -15,6 +15,11 @@ on: required: false default: true type: boolean + skip_windows_signing: + description: 'Build unsigned Windows artifacts while SignPath onboarding is pending' + required: false + default: false + type: boolean publish_draft_release: description: 'Publish manual draft artifacts to GitHub Releases' required: false @@ -52,6 +57,7 @@ jobs: SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_APPLICATION_ARTIFACT_CONFIGURATION_SLUG }} SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_INSTALLER_ARTIFACT_CONFIGURATION_SLUG }} RELEASE_DRAFT: ${{ github.event_name == 'workflow_dispatch' && inputs.draft == true }} + SKIP_WINDOWS_SIGNING: ${{ github.event_name == 'workflow_dispatch' && inputs.skip_windows_signing == true }} run: | # macOS signing + notarization is preferred: Squirrel.Mac auto-update and # first-launch Gatekeeper approval and Computer Use client attestation @@ -70,8 +76,15 @@ jobs: else echo "macos_signed=true" >> "$GITHUB_OUTPUT" fi + # A maintainer can explicitly release unsigned Windows builds while + # SignPath approval is pending, even when its configuration is present. + if [ "$SKIP_WINDOWS_SIGNING" = "true" ]; then + echo "::warning::Windows signing explicitly skipped for this manual release; Windows artifacts will be unsigned." + echo "windows_signed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi # Drafts may remain unsigned while SignPath onboarding is being tested. Tags and - # non-draft releases must have the full GitHub connector configuration available. + # non-draft releases otherwise require the full GitHub connector configuration. win_missing=() [ -n "$SIGNPATH_API_TOKEN" ] || win_missing+=("SIGNPATH_API_TOKEN secret") [ -n "$SIGNPATH_ORGANIZATION_ID" ] || win_missing+=("SIGNPATH_ORGANIZATION_ID variable") diff --git a/release-notes/v0.6.0.md b/release-notes/v0.6.0.md index 9f785e66..ebdb0508 100644 --- a/release-notes/v0.6.0.md +++ b/release-notes/v0.6.0.md @@ -23,7 +23,6 @@ We have completely rebuilt **Computer Use on macOS**, taking Codex's background- - **Task view in the sidebar**: switch from project grouping to a flat list with running sessions first, then calendar-day groups. Each task shows its workspace, and tasks waiting for permission have a distinct status indicator. - **Expanded Trace view**: inspect the opening system prompt and tool catalog from the session overview, distinguish injected context from your messages, and scan tool inputs and token counts. OpenAI Responses requests and truncated request previews are parsed more faithfully. - **Conversation-only rewind** (#1273, #962): roll back pure-chat or failed turns without restoring files, including when no file checkpoint is available. Replacement turns now use the correct checkpoint scope. -- **Windows code signing** (#1152): release applications and installers are signed through SignPath, with signature validation and regenerated update metadata after signing. ## Fixes @@ -43,6 +42,8 @@ We have completely rebuilt **Computer Use on macOS**, taking Codex's background- Download the installer for your platform and architecture from the Assets section below: +Windows applications and installers in this release are **unsigned** while SignPath approval is pending. macOS builds are signed and notarized. + - macOS: `Claude-Code-Haha-0.6.0-mac-arm64.dmg` / `Claude-Code-Haha-0.6.0-mac-x64.dmg` - Windows: `Claude-Code-Haha-0.6.0-win-x64.exe` / `Claude-Code-Haha-0.6.0-win-arm64.exe` - Linux x64: `Claude-Code-Haha-0.6.0-linux-x86_64.AppImage`, `Claude-Code-Haha-0.6.0-linux-amd64.deb` or `Claude-Code-Haha-0.6.0-linux-x86_64.rpm` @@ -78,7 +79,6 @@ Download the installer for your platform and architecture from the Assets sectio - **侧边栏任务视图**:从按项目分组切换为平铺任务列表,运行中的任务优先展示,其余按自然日归组。每项显示所属工作区,等待授权的任务有独立状态标识。 - **增强 Trace 追踪视图**:在会话概览查看起始系统提示词和工具列表,区分注入上下文与用户消息,直接浏览工具输入和 Token 用量;完善 OpenAI Responses 请求和截断请求预览的解析。 - **仅回退对话**(#1273、#962):纯聊天或失败的回合也能回退,不恢复磁盘文件,没有文件检查点时同样可用;替换回合后的检查点范围也得到修正。 -- **Windows 代码签名**(#1152):正式版应用和安装包通过 SignPath 签名,校验签名,并在签名后重新生成自动更新元数据。 ## 修复 @@ -98,6 +98,8 @@ Download the installer for your platform and architecture from the Assets sectio 请在本页面底部 Assets 中下载对应平台和架构的安装包: +SignPath 官方审批尚未完成,本次 Windows 应用和安装包**暂未签名**;macOS 安装包已签名并完成公证。 + - macOS:`Claude-Code-Haha-0.6.0-mac-arm64.dmg` / `Claude-Code-Haha-0.6.0-mac-x64.dmg` - Windows:`Claude-Code-Haha-0.6.0-win-x64.exe` / `Claude-Code-Haha-0.6.0-win-arm64.exe` - Linux x64:`Claude-Code-Haha-0.6.0-linux-x86_64.AppImage`、`Claude-Code-Haha-0.6.0-linux-amd64.deb` 或 `Claude-Code-Haha-0.6.0-linux-x86_64.rpm` diff --git a/scripts/pr/release-workflow.test.ts b/scripts/pr/release-workflow.test.ts index 08fc8f26..2548d54a 100644 --- a/scripts/pr/release-workflow.test.ts +++ b/scripts/pr/release-workflow.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from 'bun:test' -import { readFileSync, readdirSync } from 'node:fs' +import { mkdtempSync, readFileSync, readdirSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { parse } from 'yaml' describe('release desktop workflow', () => { function readReleaseWorkflow() { @@ -331,6 +334,63 @@ describe('release desktop workflow', () => { expect(workflow.indexOf('signing-preflight:')).toBeLessThan(workflow.indexOf('Upload release artifacts for final publish')) }) + test('an explicit manual Windows signing skip preserves macOS and default release requirements', async () => { + const workflow = parse(readReleaseWorkflow()) + expect(workflow.on.workflow_dispatch.inputs.skip_windows_signing).toEqual({ + description: 'Build unsigned Windows artifacts while SignPath onboarding is pending', + required: false, + default: false, + type: 'boolean', + }) + const preflight = workflow.jobs['signing-preflight'].steps.find((step: { id?: string }) => step.id === 'validate') + expect(preflight.env.SKIP_WINDOWS_SIGNING).toBe("${{ github.event_name == 'workflow_dispatch' && inputs.skip_windows_signing == true }}") + + const directory = mkdtempSync(join(tmpdir(), 'release-signing-preflight-')) + const configured = Object.fromEntries(Object.keys(preflight.env).map(key => [key, 'test-value'])) + const cases = [ + { name: 'configured release', env: {}, code: 0, outputs: 'macos_signed=true\nwindows_signed=true\n' }, + { name: 'explicit skip with configured SignPath', env: { SKIP_WINDOWS_SIGNING: 'true' }, code: 0, outputs: 'macos_signed=true\nwindows_signed=false\n' }, + { name: 'explicit skip without SignPath', env: { SKIP_WINDOWS_SIGNING: 'true', SIGNPATH_API_TOKEN: '' }, code: 0, outputs: 'macos_signed=true\nwindows_signed=false\n' }, + { name: 'release missing SignPath without skip', env: { SIGNPATH_API_TOKEN: '' }, code: 1, outputs: 'macos_signed=true\nwindows_signed=false\n' }, + { name: 'draft missing SignPath', env: { RELEASE_DRAFT: 'true', SIGNPATH_API_TOKEN: '' }, code: 0, outputs: 'macos_signed=true\nwindows_signed=false\n' }, + { name: 'explicit skip still requires macOS credentials', env: { SKIP_WINDOWS_SIGNING: 'true', CSC_LINK: '' }, code: 1, outputs: 'macos_signed=false\n' }, + ] + try { + for (const [index, scenario] of cases.entries()) { + const output = join(directory, `output-${index}`) + const logPath = join(directory, `log-${index}`) + // Capture inside the shell so Bun's test-output pipes cannot affect echo/printf exit codes. + const result = Bun.spawn(['bash', '-e', '-c', 'exec > "$PREFLIGHT_LOG" 2>&1\n' + preflight.run], { + env: { + ...configured, + RELEASE_DRAFT: 'false', + SKIP_WINDOWS_SIGNING: 'false', + ...scenario.env, + PATH: process.env.PATH, + HOME: directory, + GITHUB_OUTPUT: output, + PREFLIGHT_LOG: logPath, + }, + stdout: 'ignore', + stderr: 'ignore', + }) + const code = await result.exited + expect(code, `${scenario.name}: ${readFileSync(logPath, 'utf8')}`).toBe(scenario.code) + expect(readFileSync(output, 'utf8'), scenario.name).toBe(scenario.outputs) + } + } finally { + rmSync(directory, { recursive: true, force: true }) + } + + const unsignedBuild = extractStep(readReleaseWorkflow(), 'Build unsigned Electron release artifacts') + expect(unsignedBuild).toContain("matrix.smoke_platform == 'windows' && needs.signing-preflight.outputs.windows_signed != 'true'") + for (const name of ['Sign Windows application executables with SignPath', 'Sign Windows installer with SignPath']) { + expect(extractStep(readReleaseWorkflow(), name)).toContain("needs.signing-preflight.outputs.windows_signed == 'true'") + } + expect(extractStep(readReleaseWorkflow(), 'Verify Windows installer execution')).not.toContain('windows_signed') + expect(extractStep(readReleaseWorkflow(), 'Verify packaged app structure')).not.toContain('windows_signed') + }) + test('release workflow signs project-owned Windows binaries before packaging and repairs updater metadata', () => { const workflow = readReleaseWorkflow() const applicationConfiguration = readFileSync('.github/signpath/windows-application.xml', 'utf8')