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.
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.
* 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>
* 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>
- 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