diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c3c638..47782a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ versioning theo [SemVer](https://semver.org). **8sync rule:** mỗi PR cập nh ## [Unreleased] +## [0.110.67] — 2026-10-22 +### Fixed +- **Sweep zombie của `shot` giết nhầm shot chạy song song** (v0.110.64/.65 khớp mọi chromium `8sync-shot-*` → 2 phòng shot cùng lúc giết nhau, card #184/#185 "8sync shot fails on all WebGL pages" thiếu bằng chứng QA): giờ chỉ kill chromium mồ côi THẬT — pid cha đã chết (8sync chết giữa render). Shot sống (bất kỳ tuổi, kể cả song song) không bao giờ bị đụng. HQ auto-tick dùng cùng luật mồ côi. ## [0.110.66] — 2026-10-22 ### Fixed - **Critic sweep đụng phòng của dự án KHÔNG chạy auto** (owner: "không gửi nhầm inbox các dự án không phải chế độ 8sync ccc auto"): `sweepStuckAsks` quét mọi phòng trên máy rồi nhắn tin + đóng dialog hỏi — giờ lọc đúng `inAutoScope` + bỏ dự án bị ghim nghỉ, cùng luật với auto-tick. Rust (`ccc msg`, ship-fast/loop, relay_stuck) đã root-scope sẵn, không đổi. diff --git a/Cargo.lock b/Cargo.lock index 218015a7..a74189ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ [[package]] name = "ccc-core" -version = "0.110.66" +version = "0.110.67" dependencies = [ "anyhow", "dirs", @@ -1503,7 +1503,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "su-code" -version = "0.110.66" +version = "0.110.67" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 9cf336b8..77c141e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["crates/cli", "crates/ccc-core"] [workspace.package] -version = "0.110.66" +version = "0.110.67" edition = "2021" license = "PolyForm-Noncommercial-1.0.0" authors = ["8sync-dev <8sync.dev.1111@gmail.com>"] diff --git a/apps/hq-ui/lib/server/auto-tick.ts b/apps/hq-ui/lib/server/auto-tick.ts index 8da0e7fa..8d7b03fe 100644 --- a/apps/hq-ui/lib/server/auto-tick.ts +++ b/apps/hq-ui/lib/server/auto-tick.ts @@ -126,10 +126,11 @@ async function tickOnce(status: AutoStatus, now: number): Promise { void run("8sync", ["ccc", "auto", "revive"], REVIVE_MS); // Headless Chromium left by an `8sync shot` whose 8sync died mid-render (a guard pause // kills the room's shell tree; Chromium's children survive and burn GPU for hours — - // 2026-10-22, 47% GPU). One sweep per revive minute; live shots are younger than that. + // 2026-10-22, 47% GPU). Orphan rule only: the parent pid is gone; a live shot (any age, + // also a concurrent one) keeps its parent chain and is never touched. void run( "powershell", - ["-NoProfile", "-Command", `Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" | Where-Object { $_.CommandLine -like '*8sync-shot-*' -and $_.CreationDate -lt (Get-Date).AddMinutes(-10) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`], + ["-NoProfile", "-Command", `$p = Get-CimInstance Win32_Process; $alive = @({} + $p.ProcessId); $p | Where-Object { $_.Name -eq 'chrome.exe' -and $_.CommandLine -like '*8sync-shot-*' -and -not $alive.Contains($_.ParentProcessId) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`], REVIVE_MS, ).catch(() => {}); } diff --git a/assets/claude/.claude-plugin/plugin.json b/assets/claude/.claude-plugin/plugin.json index cc4fa456..30d3aff8 100644 --- a/assets/claude/.claude-plugin/plugin.json +++ b/assets/claude/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "8sync-kit", "displayName": "8sync kit", - "version": "0.110.66", + "version": "0.110.67", "description": "8 Sync kit for Claude Code: role-routed subagents (explore, task, quick-task, reviewer, designer, researcher, verifier, pm, architect, qa, critic) and the /ship /rules /sec /harden skills. Hooks, rules and the full skill set come from `8sync ccc kit`.", "author": { "name": "8 Sync", "url": "https://8syncdev.com" }, "homepage": "https://get.8syncdev.com", diff --git a/crates/cli/src/verbs/shot.rs b/crates/cli/src/verbs/shot.rs index b6e52b62..7dcbb858 100644 --- a/crates/cli/src/verbs/shot.rs +++ b/crates/cli/src/verbs/shot.rs @@ -217,12 +217,15 @@ const SHOT_MARK: &str = "8sync-shot-"; /// Kill Chromium processes left over from earlier shots whose `8sync` died mid-render (the auto /// guard pausing a room kills the room's shell tree; Chromium's children survive it and a stuck one /// burned ~20% GPU for hours, 2026-10-22). Ours (`keep`) is spared. Best effort, never fails a shot. -fn sweep_zombies(keep: &std::path::Path) { - let keep = keep.display().to_string(); +fn sweep_zombies(_keep: &std::path::Path) { + // Only true orphans: a marker Chromium whose parent pid is gone (the `8sync shot` that spawned + // it died — e.g. the RAM guard paused the room mid-render). A LIVE shot's Chromium keeps its + // parent chain, so concurrent shots (two rooms shooting at once, 2026-10-22) never kill each + // other — the v0.110.64 sweep matched every marker and did exactly that. #[cfg(windows)] { let script = format!( - "Get-CimInstance Win32_Process -Filter \"Name='chrome.exe'\" | Where-Object {{ $_.CommandLine -like '*{SHOT_MARK}*' -and -not ($_.CommandLine -like '*{keep}*') }} | ForEach-Object {{ Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }}" + "$p = Get-CimInstance Win32_Process; $alive = @({{ }} + $p.ProcessId); $p | Where-Object {{ $_.Name -eq 'chrome.exe' -and $_.CommandLine -like '*{SHOT_MARK}*' -and -not $alive.Contains($_.ParentProcessId) }} | ForEach-Object {{ Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }}" ); let _ = Command::new("powershell") .args(["-NoProfile", "-Command", &script]) @@ -232,12 +235,14 @@ fn sweep_zombies(keep: &std::path::Path) { } #[cfg(not(windows))] { - let _ = Command::new("pkill") - .args(["-f", &format!("chrome.*{SHOT_MARK}")]) + // Kill a marker Chromium only when its parent pid is gone (a live shot keeps its parent + // chain; concurrent shots never touch each other). + let _ = Command::new("sh") + .arg("-c") + .arg(format!("ps -eo pid=,ppid=,args= | grep '[c]hrome.*{SHOT_MARK}' | while read pid ppid rest; do kill -0 \"$ppid\" 2>/dev/null || kill \"$pid\" 2>/dev/null || true; done")) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .status(); - let _ = keep; } }