The MSI now carries REBOOT=ReallySuppress, so Windows Installer never restarts the machine when a file is in use during an in-app update, including updates driven by older updaters. The updater also passes REBOOT=ReallySuppress, a leftover instance is closed on the updater path, and the release workflow fails if the built MSI lacks the property.
Fixes#8534
Windows SQLite cannot reliably read a live Hermes WAL database through a WSL UNC path. For wsl.localhost / wsl$ Hermes paths only, read the database inside the owning distribution via wsl.exe and Python SQLite (mode=ro, query_only, one short read transaction), streaming the result back in bounded JSON frames into the existing session projection. Non-WSL paths and macOS/Linux are unchanged. Requires python3 (3.7+) and timeout inside the distribution. Fixes#8205.
The 9P share sometimes aborts a write mid-test with os error 995
(ERROR_OPERATION_ABORTED): a Claude Desktop config test passed in 1.3s
on one run and failed after 17s on the next with no code change.
Retry up to twice in the nightly only. The ci profile used by PRs keeps
failing on the first error, and nextest still lists retried passes as
FLAKY so repeat offenders stay visible.
The nightly has failed since 10-05 with LNK1327: the contract step
re-invoked cargo with TEMP on \\wsl.localhost, cargo re-linked the lib
tests, and mt.exe cannot create its manifest temp file on a UNC path.
Build the lib tests once into a nextest archive with the native TEMP,
and run both the contract test and the lib suite from that archive, so
nothing is built while TEMP points at the WSL share. The archive is
extracted to the runner temp, and --no-tests fail replaces the manual
check that the contract test was discovered.
Supersedes #6856.
Align the Tauri frontend packages with the Rust crates, include deb/rpm updater signatures in Linux releases, and publish package-specific entries in latest.json.
Fixes#4060
Signed-off-by: Xingqiu Xu <hilbertanjou83@gmail.com>
The in-app updater only follows stable releases and its manifest notes
just say "Release vX", so users jumping between stable versions never
saw what the versions in between changed. The full release notes are
too long for a popup.
- Each version gets src/whats-new/<version>.json: up to 4 one-line
items (new / fix / improve) in zh, zh-TW, en and ja, bundled into the
app at build time. "items": [] marks a version that should not pop up.
- On the first launch after an update, a dialog lists every non-empty
version between the last one seen and the current one, newest first.
Older versions beyond the first three are collapsed. Each version
links to its page in the website changelog.
- The last seen version is stored per device in settings.json
(whatsNewSeenVersion) and only moves forward, so a downgrade and
re-upgrade does not repeat anything. Without a record, only the
current version is shown. When there is nothing to show, the version
is recorded silently so the next update has a correct starting point.
- The dialog waits for the welcome and new-layout dialogs. Confirming
either of them also records the current version, so a 3.x user does
not get two dialogs in a row.
- Settings > About gets a "Recent updates" button to open the summaries
again. The 4.0.0 summary is included so that list starts from 4.0; no
update popup can reach it, since only 4.0.2 and later record a version.
- The release workflow first requires the tagged version's summary file
and validates every bundled summary with the unit test, since a
malformed item would crash the dialog and put the whole app on the
error screen.
- macOS used one runner that compiled aarch64 and x86_64 one after the
other inside `tauri build --target universal-apple-darwin`, and every
notarization retry recompiled both. Now `macos-binary` compiles each
arch on its own runner with --no-bundle, and `macos-release` merges
them with lipo and runs `tauri bundle --target universal-apple-darwin
--bundles app`. Only that bundle/sign/notarize step is retried. The
styled DMG from create-dmg, the zip and the updater tar.gz are made as
before. `macos-binary` carries the release environment, so a release
is still approved once at the start; `macos-release` must not, or it
would wait for a second approval halfway.
- The Tauri signing key preparation moves into a composite action shared
by the Linux/Windows matrix and `macos-release`. Its output matches
the old inline script for every key format tried.
- Windows x64 builds only the MSI. Tauri also built an NSIS setup.exe
that was never uploaded; the portable zip still comes from the exe.
- latest.json is generated inside publish-release from the downloaded
signatures, before the release is created, so a missing platform
fails without leaving a half-published release. The
assemble-latest-json job is gone.
- workflow_dispatch runs the full build, signing and notarization as a
dry run, with assets kept as workflow artifacts. Only a tag push
publishes, even when a dispatch is started on a tag; the concurrency
group includes the event name so a dry run cannot cancel a release in
progress; and `/` in branch names becomes `-` in asset file names.
- Quote the $GITHUB_OUTPUT redirects that shellcheck flagged.
Pushes to main ran every job on the theory that they keep the caches
warm, but a cache whose key hits exactly is never saved again, so those
full runs refreshed nothing. Pushes now skip unaffected jobs like pull
requests do.
A scheduled run every Monday (03:41 Beijing) and workflow_dispatch run
everything, rebuilding caches after eviction: the filter step only runs
for pull_request and push, and the `|| 'true'` fallback on its outputs
turns a skipped filter into "both areas changed". The concurrency group
now includes the event name so the scheduled run and a push to main do
not cancel each other.
- Backend tests run with `cargo nextest run --profile ci`: one process
per test, a 180s per-test timeout (src-tauri/.config/nextest.toml),
and no fail-fast. The WSL2 nightly uses it too, still one test at a
time and with the same selection as the old `--skip database::`, so a
hung test is killed instead of eating the whole job.
- Integration tests shared one fixed test home, and each process wiped
it on startup. Under nextest they ran concurrently and failed en
masse, so tests/support.rs now picks a directory per
NEXTEST_TEST_GLOBAL_SLOT. Plain `cargo test` keeps the old directory.
- Swatinem/rust-cache replaces the hand-rolled actions/cache step. It
caches dependencies only, not the cc-switch crate that is rebuilt
every run, and keys on OS, rustc version and Cargo.lock. The WSL2 CI
job and the nightly share the `backend` key but only restore it, so
their lib-only builds never save over the full Windows cache.
- Quote the $GITHUB_OUTPUT redirect that shellcheck flagged.
dtolnay/rust-toolchain@stable installed the latest stable, then rustup
switched to the 1.95 channel from rust-toolchain.toml on the first cargo
call, so every job downloaded two toolchains. Read the channel from
rust-toolchain.toml and pass it as `toolchain` in the CI backend and
WSL2 jobs, the release build and the WSL2 nightly, keeping the file as
the single source of the compiler version.
- macOS: tee each build attempt to a log, and stop on HTTP 401/403 from
Apple instead of retrying. An expired developer agreement cannot be
fixed by a retry; for v4.0.0 the loop rebuilt both architectures twice
more and ran into the job timeout.
- Treat a missing macOS .tar.gz.sig, Windows MSI or MSI signature
(either arch), AppImage or AppImage signature as an error. They used to
print a warning, and latest.json then silently left that platform out,
so its users would get no update.
- assemble-latest-json downloads only the *.sig files it reads and fails
if any of the six platforms lacks a signature.
The nightly has not passed in weeks: 37 tests failed on the
\\wsl.localhost share before the run hung. They fall into a few causes
that come from the 9P filesystem, not from product bugs:
- Windows cannot lock SQLite files there, so every on-disk SQLite
fixture fails with "database is locked". Skip `database::` by name and
let fixture tests return early via config::sqlite_unsupported_in_temp_dir.
The account recovery test now uses an in-memory DB that it reuses
across the simulated restart.
- Integration tests all create the app database, so run --lib only.
Users keep that database in the Windows home unless they override the
app config dir.
- The share exposes no file identity, so parent timelines are not cached.
The five cache tests now check reuse only where parent_cache_supported.
- Remote symlinks are not followed. The catalog escape test asserts None
when the link resolves, and otherwise that the result is unreadable.
With one test binary left, the timeout drops from 90 to 30 minutes.
* ci: run WSL2 contract tests via prebuilt binaries
link.exe/mt.exe fails to create manifests when TEMP points to a WSL UNC path (LNK1327). Run precompiled test binaries directly instead of re-invoking cargo, and explicitly set native TEMP during compilation.
* ci: harden prebuilt WSL2 contract runner
Keep the scheduled full-suite workflow unchanged until it can be validated independently. Drain Cargo's JSON stream before using the test binary and propagate its exit status.
---------
Co-authored-by: saladday <1203511142@qq.com>
Add a changes-detection job (dorny/paths-filter, SHA-pinned) and gate
the frontend job, the 3-OS backend matrix, and the WSL2 contract gate
on whether the PR actually touches their inputs. About two thirds of
merged PRs only touch one side, and docs-only PRs now skip CI entirely.
Pushes to main still run everything unconditionally so the caches that
PRs restore from stay warm. Path groups mirror .github/labeler.yml,
plus rust-toolchain.toml which pins the compiler used by the backend
jobs.
* ci: add WSL2 backend checks
* ci: fix WSL2 backend job by keeping MSVC toolchain on native temp
- compile test binaries before pointing TEMP/TMP at the WSL2 UNC path
(mt.exe fails with c1010070/LNK1327 when TEMP is a UNC path)
- inject WSL2 TEMP/TMP per test step instead of via GITHUB_ENV
- use --tests so doctests cannot trigger link steps under the UNC temp
- run the filesystem contract gate before the full serial suite
- drop fmt/clippy steps duplicated from the existing Windows job
* ci: move full WSL2 suite to nightly, keep per-PR contract gate
- the full serial suite needs 50+ minutes on the 9P filesystem
(2026/2343 lib tests finished in 39.5min before the 45min timeout)
- per-PR job now runs only the atomic-write contract (~6min total)
and builds just the lib test binary via --lib
- new nightly workflow (schedule + workflow_dispatch, 90min timeout)
runs the contract plus the full serial suite on the WSL2 home
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(security): harden usage script execution and Grok session log collection
Three hardening fixes in v3.19.0 code:
1. **usage_script: cap JS runtime resources and execution time**
`execute_usage_script` used a vanilla `Runtime::new()` with no limits,
so a malicious or buggy script delivered via deeplink or synced DB could
hang the backend thread forever with `while(true)`. Add
`create_script_runtime()` enforcing a 5-second interrupt handler, 16 MiB
memory limit, and 256 KiB stack limit. Regression test confirms an
infinite-loop script is rejected within seconds instead of blocking.
2. **Grok session log: skip oversized files**
`sync_single_grok_file` loaded the entire `updates.jsonl` into memory
with no size cap. Add a 50 MiB limit and skip files that exceed it.
3. **Grok session log: bound directory traversal**
`collect_files_named` recursed without depth limits and followed
symlinks, so a symlink cycle under `~/.grok/sessions` caused a stack
overflow. Add a 16-level depth limit and skip symlinks entirely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): show hidden usage access token and user id in deeplink provider import dialog
The provider import confirmation dialog parsed usageAccessToken and
usageUserId from ccswitch:// URLs and persisted them in
ProviderMeta.usage_script, but never rendered them. Users could not see
these credentials before approving the import.
- Add i18n keys for usageAccessToken/usageUserId in all locales.
- Render both fields in the usage script section; mask the access token
like usageApiKey.
- Add a regression test verifying the dialog displays the masked token
and the user id.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(security): bound Codex model catalog and proxy response bodies
Two issues were found while auditing security boundaries:
1. **Codex model_catalog_json arbitrary file read**
only checked the filename and
accepted any absolute path. A malicious could
point CC Switch at an external file (e.g. ) or a huge
file to exhaust memory.
- Require the resolved path to stay inside
using a new helper (lexical, no filesystem calls).
- Treat Unix-style absolute paths as absolute on Windows.
- Cap catalog reads at 32 MiB.
2. **Unbounded proxy response bodies / compression bombs**
Non-streaming responses and error responses were read with
and fully decompressed without any byte
limit, allowing a malicious upstream to OOM the backend.
- Add with a 128 MiB ceiling.
- Reject oversized bodies and oversized decompressed payloads with
.
- Map the new error variant into the Codex proxy error response.
Regression tests cover: absolute-path containment, traversal rejection,
oversized catalog file, oversized buffered/streamed proxy responses.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ci): set up pnpm via Corepack instead of pnpm/action-setup v6
pnpm/action-setup v6 installs pnpm through a two-step self-installer:
it bootstraps the latest pnpm (11.7.0) via npm, then downloads the
pinned version (10.12.3) from the npm registry to switch. When that
second tarball fetch failed (error 23), the self-installer still
reported "done" and crashed with ENOENT on @pnpm/linux-x64's
package.json, failing the CI setup step.
Replace the action with Node 20's bundled Corepack and pin the version
once via package.json's packageManager field, so setup performs a
single exact-version download with no floating bootstrap. This also
lets release.yml drop its Windows ARM64 special case, since Corepack
has no arch restrictions, and pins the same version for local dev.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(security): close review gaps in proxy body caps, deeplink dialog, catalog path
Address maintainer review on the security PR:
- Proxy: bound the four remaining unbounded forwarder read paths
(non-2xx error body, failover pre-read, both 2xx error-envelope
validators) with bytes_with_limit + bounded decompression.
- Proxy: bytes_with_limit now accumulates Hyper/Reqwest/Streamed bodies
chunk-by-chunk and aborts mid-stream instead of collecting the full
body before comparing; Buffered keeps the post-hoc check.
- Proxy: decompression is budgeted at the decoder (Read::take on
gzip/deflate/zstd, bounded brotli reader) via decompress_body_with_limit,
so bombs are cut off at the byte budget instead of after full expansion,
including stacked-encoding intermediates.
- Deeplink: show the usage section whenever any usage field is present,
matching build_provider_meta persistence — a deeplink carrying only
usageAccessToken/usageUserId no longer imports credentials invisibly.
- Codex catalog: after the lexical containment check, canonicalize the
existing file and re-verify containment so symlinks inside ~/.codex
cannot escape the config dir; the path_is_within doc comment no longer
oversells the lexical check as symlink-safe.
- Grok sync: comment now matches the skip-all-symlinks behavior and each
skip is logged, so sessions behind a symlink are diagnosable.
- Docs: CONTRIBUTING notes the packageManager pnpm pin and Corepack flow.
Regression tests follow the same methodology as round 1: reverting a
guard fails the corresponding test (server write-count assertions prove
the mid-stream abort; a truncated gzip stream distinguishes TooLarge
from read-to-end errors; symlink escape is rejected only after the
canonicalize re-check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Move the R2 sync out of release.yml into a standalone sync-r2.yml
triggered on release promotion (release: released) or manual dispatch,
so the mirror follows the same gate as GitHub's /releases/latest
- Hard-fail on the official repo when R2 secrets are missing (a silently
stale mirror strands updater users); forks may still skip
- Only the tag that is currently releases/latest may rewrite the root
manifests or prune old versions; backfills of older tags restore
versioned files only
- Resolve releases/latest with retries and fail instead of guessing on
API errors; re-verify right before publishing the root manifests
- Add scripts/rewrite-updater-manifest.mjs to point latest.json download
URLs at the mirror (minisign signatures cover file contents and stay
valid); upload the macOS .tar.gz updater payload alongside installers
- Put https://dl.ccswitch.io/latest.json first in the updater endpoints
with GitHub as connectivity fallback
The manifest generator now hashes each installer (SHA-256) so the
website download page can display verifiable checksums, and accepts an
optional pub-date argument. The sync-to-r2 job passes the release's
actual publishedAt instead of relying on generation time, which drifted
for backfills.
Add a sync-to-r2 job that downloads the published release assets,
generates the website download manifest, uploads both to the
cc-switch-releases bucket (dl.ccswitch.io), and prunes versions
beyond the latest five. The job skips itself when R2 secrets are
not configured, so forks and the current pipeline are unaffected.
* fix(ci): run backend checks on Windows/macOS and repair platform-gated tests
The backend CI job ran only on ubuntu-22.04, so every #[cfg(target_os =
"windows")] / macOS path — tests and clippy lints alike — was never
compiled or run. As a result main shipped 8 failing Windows unit tests and
a hard `cargo clippy -- -D warnings` failure on Windows, all invisible to
CI because it only builds and tests Linux.
Changes:
- ci: run the backend job on a {ubuntu-22.04, windows-latest, macos-latest}
matrix (fail-fast: false); gate the apt install step on runner.os ==
'Linux' and use `shell: bash` for the dist placeholder so it works on all
three runners.
- misc.rs: fix 6 stale `anchored_upgrade_windows` tests. Commit 5092fe51
removed codex from `prefers_official_update`, so codex now anchors to the
package manager with no `codex update ||` prefix; update the five
package-manager expectations accordingly, and retarget the no-sibling
"official-update-without-fallback" test to `claude` (still in the list) so
that branch stays covered instead of being silently dropped.
- codex_state_db.rs / codex_history_migration.rs: the two sqlite_home tests
built TOML basic strings from Windows paths, whose backslashes are invalid
escape sequences and made the override fail to parse; switch to TOML
literal (single-quoted) strings so the path is taken verbatim.
- clippy (13 Windows-only warnings): move `use std::io::Write` into the
#[cfg(unix)] block that actually uses it; convert 3 unneeded `return`s in
Windows-gated tail positions to expressions; mark 9 POSIX-shell helpers
#[cfg_attr(windows, allow(dead_code))] since they are used only by the
macOS/Linux terminal launchers.
Verified locally (Windows 11, Rust 1.95.0):
cargo test --lib → 1748 passed; 0 failed (was 1740/8)
cargo clippy -- -D warnings → clean (was 13 errors)
cargo fmt --check → clean
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(skills): resolve home via get_home_dir so tests isolate on Windows
services/skill.rs called dirs::home_dir() directly in five places. On
Windows dirs::home_dir() resolves through the Known Folder API and
ignores HOME/USERPROFILE, so the CC_SWITCH_TEST_HOME override set by the
test harness never applied: the skill_sync integration tests scanned the
runner's real user profile, found no skills, and failed (the first panic
then poisoned the shared test mutex, cascading to all seven). On Unix
the harness also sets HOME, which dirs::home_dir() honors, so the suite
passed there by accident.
Route all five call sites through crate::config::get_home_dir(), the
crate-wide home resolver that prefers CC_SWITCH_TEST_HOME. The three
now-unreachable GET_HOME_DIR_FAILED error branches are dropped:
get_home_dir() is infallible, matching every other config-dir resolver.
Production behavior is unchanged (CC_SWITCH_TEST_HOME is unset outside
tests, so get_home_dir falls through to dirs::home_dir).
Add a regression test that discriminates on every platform by pointing
CC_SWITCH_TEST_HOME at a temp dir different from $HOME; it is marked
#[serial_test::serial] to stay mutually exclusive with the other tests
mutating the same process-global variable. Verified the test fails
against the old code on macOS with the same failure mode as Windows CI.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
- Add CODEOWNERS so PRs to main require owner review (closes the gap
where two collaborators could approve each other's changes).
- Gate the release job behind a 'release' environment, so signing
secrets are only unlocked after manual approval.
- Stop ignoring the whole .github directory; this had been silently
swallowing labeler.yml and workflows/labeler.yml, which are now tracked.
Paired with a tag ruleset (created out-of-band) that restricts creation
of v* tags to admins, this prevents a write collaborator from pushing a
tag to trigger a signed release build.
* ci: add Windows ARM64 release artifacts
* ci: keep release matrix jobs independent
* ci: fix pnpm cache path on Windows runners
* ci: setup pnpm with corepack on Windows ARM64
* ci: fix Windows ARM64 release build
* ci: retry transient release bundler downloads
* ci: remove non-minimal release workflow changes
* ci: keep release matrix jobs independent
macOS signing fails in forks without Apple secrets and, with default
matrix fail-fast, cancels the sibling jobs (including Windows ARM64)
before they finish. Disable fail-fast so each platform runs to completion.
---------
Co-authored-by: MoonDreamStars <moondreamstar1@gmail.com>
Each tagged release now leads with the canonical official website
in three languages, ensuring every Release page (which is indexed
independently by Google) becomes a dofollow backlink to ccswitch.io.
Prevents `git fetch origin pull/<N>/head:main` failures on fork PRs
whose head branch is also named `main` — using a SHA puts the runner
in detached HEAD so the main ref is free for the action's internal fetch.
Cap was too tight — review tasks need 8-15 turns to read files, analyze,
and post results. The first run after the previous prompt change failed
with error_max_turns at turn 6. The disallowedTools list already keeps
the agent in read-only mode, so an explicit turn cap is redundant.
Previous prompt produced 5-10 findings per PR including dead-code/style
nits mixed with real bugs. New prompt adds severity tiering, an 80
confidence threshold, an explicit anti-pattern list, a 5-nit cap, and
permits zero-comment LGTM as a valid outcome. Calibration follows
Anthropic Code Review docs and the claude-plugins-official prompt.
- Restrict to repo collaborators via author_association check
- Use OAuth token for subscription billing
- Disable Edit/Write tools and set contents:read permission to enforce review-only
- Mark issues as stale after 60 days of inactivity
- Auto-close after 14 more days without response
- Bilingual (zh/en) stale and close messages
- Exempt security and performance labeled issues
- Runs daily at UTC 00:00, processes up to 100 issues per run
- Only affects issues, not pull requests
- Use get_home_dir() instead of dirs::home_dir() in get_opencode_dir()
and get_openclaw_dir() to respect CC_SWITCH_TEST_HOME override
- Add CC_SWITCH_TEST_HOME to all TempHome implementations
- Add #[serial] to all with_test_home tests to share serialization
with other env-mutating tests
- Remove --test-threads=1 workaround from CI
Tauri's built-in DMG styling relies on AppleScript/Finder access which
silently fails on CI (tauri-apps/tauri#1731). Switch to create-dmg tool
which works on GitHub Actions macOS runners.
- Replace Tauri DMG with create-dmg: background image, icon positions,
app-drop-link, codesign, hide-extension
- Regenerate background image at 2x Retina resolution (1320x800)
- Revert tauri.conf.json dmg config (ineffective on CI)
- Reorder steps: Prepare → Notarize DMG → Verify
- Notarize and Verify now use release-assets/ path for DMG
- Add DMG background image with drag-to-install arrow guide
- Configure window size (660x400), app and Applications icon positions
- Center icons horizontally with visual arrow between them
Tauri only notarizes the .app bundle, not the DMG container. This caused
stapler staple to fail with "Record not found" for the DMG.
- Add "Notarize macOS DMG" step using xcrun notarytool with retry logic
- Add retry logic (3 attempts) to macOS build step for transient network failures
- Add hdiutil verify before DMG notarization submission