Keep first-run CLI setup local-first by building only local onboarding steps.
Remove the startup preflight that pinged Anthropic/OAuth endpoints before any explicit login or model request.
Fixes#859.
Tested: bun test src/components/onboardingSteps.test.ts
Tested: bun run check:server
Not-tested: bun run verify; check:coverage; check:policy
Confidence: high
Scope-risk: narrow
#865 added guards against the prewarm_session/user_message concurrency
race, but both keyed on isSessionTurnActive (messageSent===true).
messageSent only flips true after CLI startup completes, so during the
startup window a user turn is registered yet invisible to the guards — the
prewarm idle timer could still arm on, and later fire against, an active
turn, killing the CLI and leaving the UI stuck on "执行中".
- Add hasPendingOrActiveUserTurn() (turn registered regardless of
messageSent) and use it for both prewarm guards, covering the blind window.
- Structurally disarm the timer when a turn completes (clearPrewarmState in
bindActiveUserTurnCompletion), so a timer armed by the race never outlives
the turn regardless of ordering.
- Add test hooks + 3 regression tests: blind-window turn not killed, active
turn not killed, truly-idle session still reclaimed.
Make workflow_dispatch draft handling explicit and add a post-publish guard that edits the release back to draft when manual draft runs upload assets to an existing release.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nScope-risk: narrow\nConfidence: high
Remove macOS download quarantine/provenance attributes from the packaged node-pty runtime cache before loading the native module. Existing matching caches are repaired before require, and read-only helper permissions are restored after cleanup.\n\nTested: bun test desktop/electron/services/terminal.test.ts\nTested: cd desktop && bun run build:electron\nTested: git diff --check\nTested: prepared the installed 0.4.3 node-pty cache and required it with Electron's Node runtime\nScope-risk: narrow\nConfidence: high
Replace electron-builder's internal macOS notarization wait with an explicit notarytool flow: build a signed app with update config, submit it with a bounded notarytool timeout, staple and validate it, then rebuild dmg/zip from the notarized app via --prepackaged. Keep draft-only signed/no-notary builds available for fast artifact checks.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nTested: bun run scripts/release.ts 0.4.3 --dry\nTested: local arm64 signed/no-notary build followed by --prepackaged dmg/zip rebuild\nTested: bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron\nTested: codesign --verify --deep --strict --verbose=2 desktop/build-artifacts/electron/mac-arm64/Claude\ Code\ Haha.app\nConfidence: medium\nScope-risk: moderate
Add a workflow_dispatch-only notarize_macos switch so draft release runs can build Developer ID signed macOS artifacts without waiting on Apple notarization when GitHub runner networking is failing. Tag push releases still default to notarization and Gatekeeper release checks.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nTested: bun run scripts/release.ts 0.4.3 --dry\nConfidence: medium\nScope-risk: moderate
Capture the signed macOS electron-builder exit code before the retry branch so watchdog timeouts and notarization failures fail the signed step directly instead of falling through to package-smoke.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nTested: bun run scripts/release.ts 0.4.3 --dry\nTested: local bash watchdog timeout returns status 124\nConfidence: high\nScope-risk: narrow
Add an outer watchdog around the signed macOS electron-builder step so hung notarytool waits return control to the retry loop instead of idling until the GitHub step timeout.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nTested: bun run scripts/release.ts 0.4.3 --dry\nConfidence: medium\nScope-risk: narrow
Tested: bun test scripts/pr/release-workflow.test.ts
Tested: git diff --check
Tested: bun run scripts/release.ts 0.4.3 --dry
Confidence: high
Scope-risk: narrow
Tested: bun test scripts/pr/release-workflow.test.ts
Tested: git diff --check
Tested: bun run scripts/release.ts 0.4.3 --dry
Tested: env DEBUG=electron-builder,electron-osx-sign node ./node_modules/electron-builder/out/cli/cli.js --mac zip --arm64 --publish never -c.mac.notarize=false
Tested: bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron
Confidence: high
Scope-risk: narrow
Tested: bun test scripts/pr/release-workflow.test.ts
Tested: git diff --check
Tested: bun run scripts/release.ts 0.4.3 --dry
Confidence: high
Scope-risk: narrow
Tested: bun run scripts/release.ts 0.4.3 --dry
Tested: git diff --check
Tested: bun run verify (artifacts/quality-runs/2026-06-16T16-48-12-824Z/report.md; passed=8 failed=0 skipped=2)
Confidence: high
Scope-risk: narrow
Tested: bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts scripts/quality-gate/package-smoke/index.test.ts
Tested: bun run check:policy
Tested: bun run check:docs
Tested: workflow YAML parse and git diff --check
Scope-risk: moderate
Mid-stream api_error/overloaded_error SSE events (e.g. a local provider such
as LM Studio rejecting a malformed tool_call) arrive inside the 200 SSE body,
bypassing withRetry — which only guards stream creation — so they were
surfaced as a terminal API error that ended the turn with no retry.
Add withStreamRetry around queryModel: when an attempt produced zero output
(no tool_use block completed, so query.ts never started a tool — safe w.r.t.
the double-tool-execution hazard, inc-4258), re-establish the stream and retry
up to CLAUDE_STREAM_TRANSIENT_RETRY_MAX times (default 2) before surfacing the
error. Covers both the streaming and non-streaming query wrappers; adds unit
tests for the retry loop and the transient-error classifier.
Add per-model 1M capability flags for custom providers and persist them through provider settings.
When a model is marked as 1M-capable, runtime env uses the local [1m] marker and the desktop form auto-fills a 1000000-token context window when needed.
Tested: bun test src/server/__tests__/providers.test.ts -t "custom providers can mark main and role models as 1M-capable"
Tested: cd desktop && bun run test -- generalSettings.test.tsx -t "normalizes blank model mappings|saves 1M model declarations"
Tested: bun run check:desktop
Tested: bun run check:server
Not-tested: bun run verify / coverage gates were not run for this scoped local handoff.
Confidence: high
Scope-risk: moderate
Background (run_in_background) subagents run detached from the main query loop, so their tool_use/tool_result never reached the desktop and their cards stayed on "no tool activity" even after completion. Route the activity through the SDK event queue as agent_tool_activity, re-emitted by the ws handler as tool_use_complete/tool_result tagged with the parent Agent tool_use id, so the UI groups them under the agent card like synchronous subagents.
The configured request timeout (API_TIMEOUT_MS) only feeds the SDK client
timeout, which on a streaming request is cleared the moment response headers
arrive. Slow local/3P models return 200 headers fast but spend minutes
prefilling before the first SSE token, so that window was governed by the idle
watchdog (CLAUDE_STREAM_IDLE_TIMEOUT_MS, 240s) -- a separate env users cannot
configure. The configured timeout therefore never applied to slow prefill, so
raising it had no effect (#826, follow-up to #449).
Split the stream watchdog into phases: use a first-token budget (tied to the
user's request timeout via CLAUDE_STREAM_FIRST_TOKEN_TIMEOUT_MS) until the first
chunk arrives, then switch to the shorter mid-stream idle budget. The overall
max-duration cap (#766) still backstops trickling bodies; CLI defaults fall back
to prior behavior.
Add a provider-level Tool Search capability that defaults on for native Anthropic Messages providers and writes ENABLE_TOOL_SEARCH through the managed provider env path. Keep OpenAI proxy formats opted out because the proxy transforms do not support Anthropic tool_reference history yet.
Preserve upgrade safety by normalizing missing or stringly typed stored values instead of dropping saved providers.
Tested: bun test src/server/__tests__/provider-runtime-env.test.ts src/server/__tests__/providers.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx --run
Tested: cd desktop && bun run lint
Tested: bun run check:server
Confidence: high
Scope-risk: moderate
Persist runtime provider metadata for desktop sessions so context estimates stay tied to the provider that launched the session. Infer a unique saved provider window for older sessions without runtime metadata, and keep Xiaomi MiMo custom providers on thinking-only capability mapping instead of max-effort passthrough.
Tested: bun test src/server/__tests__/conversations.test.ts -t "unique saved provider context window|pass the active provider id|provider model context windows"
Tested: bun test src/server/__tests__/providers.test.ts -t "Xiaomi MiMo custom providers|custom providers declare|model context windows"
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/searchService.sessions.test.ts src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts src/stores/providerStore.test.ts src/components/controls/ModelSelector.test.tsx
Not-tested: full bun run verify was not run for this local issue handoff.
Confidence: high
Scope-risk: moderate
Persist transcript usage through session history so reopened desktop tabs can restore header token counts, and resume elapsed timers when reconnected sessions report active status.
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run lint
Tested: bun run check:server
Tested: git diff --check
Not-tested: bun run verify and coverage; scoped local handoff, not PR-ready validation.
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts -t "command breadcrumb"
Tested: bun test src/server/__tests__/searchService.sessions.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Scope-risk: narrow
Confidence: high
Tested: cd desktop && bun run test --run src/lib/desktopHost/contract.test.ts src/pages/TerminalSettings.test.tsx src/lib/desktopHost/electronHost.test.ts electron/ipc/capabilities.test.ts
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun run check:native
Not-tested: bun run check:coverage
Confidence: high
Scope-risk: moderate
Keep programmatic scroll suppression limited to scroll events that still match the requested auto-scroll target. This lets user scrollbar movement take over while active thinking content is growing.
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
Tested:
- bun test src/server/__tests__/memory.test.ts
- cd desktop && bun run test -- src/__tests__/memorySettings.test.tsx
- cd desktop && bun run test -- src/components/chat/MessageList.test.tsx -t "active tool input"
- cd desktop && bun run check:desktop
- bun run check:server
Tested: cd desktop && bun run test -- --run src/components/workbench/WorkbenchPanel.test.tsx
Tested: cd desktop && bun run test -- --run src/components/layout/ContentRouter.test.tsx
Tested: cd desktop && bun run test -- --run src/stores/tabStore.test.ts
Tested: bun run check:desktop
Confidence: high
Scope-risk: moderate
Add a Codex-style global search dialog (Cmd+K / sidebar button) that
full-text searches across all session transcripts, replacing the old
title-only sidebar filter.
Backend: rewrite searchService.searchSessions as a two-phase engine —
ripgrep finds candidate files + matched lines, then those lines are
parsed to keep only user/assistant text, re-confirmed against the
cleaned text to drop JSON/UUID/base64 false positives, and windowed into
highlighted snippets. Results carry real session titles (new
sessionService.getSessionTitleAndMeta reusing the list title
precedence), project path, mtime, role and match counts; falls back to a
JS scan when ripgrep is unavailable.
Frontend: new GlobalSearchModal (debounced, stale-response-safe, keyboard
nav, role badges, highlighting, recent-chats empty state); Cmd+K now
opens it; the sidebar input is replaced by a search trigger button.
Tests: 15 backend cases (searchService.sessions) + 12 frontend cases
(GlobalSearchModal); existing Sidebar/pages tests updated for the new
trigger.
Tested:
- cd desktop && bun run test src/components/chat/CodeViewer.test.tsx
- cd desktop && bun run test src/components/chat/chatBlocks.test.tsx
- cd desktop && bun run lint
- bun run check:desktop
Confidence: high
Scope-risk: narrow
Prefer the live git diff for currently changed workspace files so DIFF previews do not get stuck on stale transcript snapshots. Refresh an existing workspace preview tab when it is opened again instead of only reactivating cached content.
Tested: bun test src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test src/stores/workspacePanelStore.test.ts --run
Tested: bun run check:server
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
Output target detection should not promote URLs or markdown links embedded in fenced log/code blocks into assistant output cards. Keep explicit localhost and file references outside code blocks routable.
Tested: cd desktop && bun run test -- src/lib/assistantOutputTargets.test.ts src/components/chat/AssistantMessage.linkrouting.test.tsx
Tested: cd desktop && bun run lint
Confidence: high
Scope-risk: narrow
Keep the file navigator hidden when a preview is active so code and diff tabs get the full workspace width.
Defer all-files tree loading until the navigator is explicitly opened to avoid unnecessary tree requests while previewing.
Tested: cd desktop && bun run test src/components/workspace/WorkspacePanel.test.tsx --run
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
Add live line and character stats to desktop tool cards while Write and Edit inputs stream, and show Writer preview stats before the 120-line windowing threshold.
Tested: cd desktop && bun run test -- --run src/components/chat/chatBlocks.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Not-tested: bun run verify / coverage; scoped desktop UI handoff only.
Confidence: high
Scope-risk: narrow
When the user stops generation, finalize local streaming state by flushing any buffered assistant text into the transcript and marking pending tool-use cards as stopped instead of leaving them in a generating state.
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/components/chat/chatBlocks.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Confidence: high
Scope-risk: narrow
Third-party gateways can trickle content deltas (e.g. a large tool_use
input_json_delta) just under the idle-watchdog window and never send
message_stop. Each delta resets the idle timer, so the in-kernel watchdog
never fires and the request hangs forever — the desktop shows "running"
with slowly growing tokens and a permanently-pending trace span (#766).
Add an overall-duration watchdog (CLAUDE_STREAM_MAX_DURATION_MS) that is
armed once and never reset by incoming chunks; the desktop injects 600000ms
via buildChildEnv. Orthogonal to the idle watchdog: idle catches a fully
silent stream, this catches an endless trickle that never completes. CLI
default stays 0 (disabled) for backward compatibility.
Verified end-to-end by replaying the issue's real captured SSE against the
actual kernel: trickle hangs without the cap, aborts cleanly with it.
Keep provider ordering compatible with older desktop store state, keep workspace traversal blocked even when external changed-file roots are registered, and align the quality-contract test with the current AGENTS wording.
Tested: bun test scripts/pr/quality-contract.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/__tests__/skillsSettings.test.tsx src/__tests__/pluginsSettings.test.tsx src/__tests__/diagnosticsSettings.test.tsx --run
Tested: bun run check:policy
Tested: bun run check:desktop
Tested: bun test src/server/__tests__/workspace-service.test.ts -t 'does not allow relative traversal'
Tested: bun test src/server/__tests__/sessions.test.ts -t 'workspace/file and tree should reject traversal|workspace/diff should reject traversal' --timeout=20000
Confidence: high
Scope-risk: narrow
Fix four root causes in the desktop preview pipeline, surfaced when the
model writes the files the user pointed it at:
- Output chips guessed paths from prose and could point at a missing file.
They are now reconciled against the turn's real changed files: a bare
`index.html` resolves to the `todo-app/index.html` actually written, and
mentions the turn never changed are dropped.
- A standalone single-page index.html got no browser preview (mistaken for a
Vite template). It is now only routed to the source view when a
package.json/vite.config ships in the same change-set.
- Files written outside the session workdir (another folder, or another drive
on Windows) failed to preview with 'Path is outside workspace'. The turn's
changed-file directories are registered as filesystem access roots; html
serves via /local-file and other files via a workdir-relaxed read.
- The visual-selection prompt leaked as a raw bubble on Windows because the
server-appended '[Image source: ...]' line broke replay dedupe. Replay text
is now metadata-normalized before comparison (affects any image message).
Adds unit tests for each: htmlPreviewPolicy, assistantOutputTargets
reconciliation, replay dedupe + stripGeneratedImageMetadataLines, filesystem
access roots, and workspace outside-workdir reads.