From 0f74fd1a103af0430b6d8bc36dde96ec19ecc6d4 Mon Sep 17 00:00:00 2001 From: aannoo Date: Mon, 6 Jul 2026 19:00:04 +0100 Subject: [PATCH 1/4] release: v0.7.23 --- Cargo.lock | 2 +- Cargo.toml | 2 +- dist-workspace.toml | 3 +++ pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ade308..4f7cd34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -872,7 +872,7 @@ dependencies = [ [[package]] name = "hcom" -version = "0.7.22" +version = "0.7.23" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index 20ff60d..1f132e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hcom" -version = "0.7.22" +version = "0.7.23" edition = "2024" authors = ["hcom"] description = "Connect Claude Code, Gemini CLI, Codex, OpenCode, Kilo Code, Pi, Oh My Pi, Antigravity, Cursor, Kimi, and Copilot so agents can message, watch, and spawn each other across terminals" diff --git a/dist-workspace.toml b/dist-workspace.toml index 73dd375..d7dbb13 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -7,6 +7,9 @@ members = ["cargo:."] cargo-dist-version = "0.31.0" # CI backends to support ci = "github" +# Build and upload every release artifact on the release PR without publishing. +# Revert to the default "plan" mode after v0.7.23 ships. +pr-run-mode = "upload" # Target platforms to build apps for (Rust target-triple syntax) targets = ["aarch64-apple-darwin", "aarch64-linux-android", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] # The installers to generate for each app diff --git a/pyproject.toml b/pyproject.toml index 4435516..a4928d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "hcom" -version = "0.7.22" +version = "0.7.23" description = "Let AI agents message, watch, and spawn each other across terminals. Claude Code, Gemini CLI, Codex, OpenCode, Kilo Code, Pi, Oh My Pi, Antigravity, Cursor, Kimi, Copilot." readme = "README.md" license = "MIT" From cc69c963daa3d9c5a6cade35329f4c821335c8b2 Mon Sep 17 00:00:00 2001 From: aannoo Date: Mon, 6 Jul 2026 19:02:40 +0100 Subject: [PATCH 2/4] ci: regenerate release workflow for artifact preflight --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d650985..bf08e76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,6 +148,10 @@ jobs: echo "CC_aarch64_linux_android=$NDK_BIN/aarch64-linux-android24-clang" >> "$GITHUB_ENV" echo "AR_aarch64_linux_android=$NDK_BIN/llvm-ar" >> "$GITHUB_ENV" shell: "bash" + - uses: swatinem/rust-cache@v2 + with: + key: ${{ join(matrix.targets, '-') }} + cache-provider: ${{ matrix.cache_provider }} - name: Install dist run: ${{ matrix.install_dist.run }} # Get the dist-manifest From 9fcd6cec188273650a46b10b2ddb9c84e1476fd6 Mon Sep 17 00:00:00 2001 From: aannoo Date: Mon, 6 Jul 2026 19:25:49 +0100 Subject: [PATCH 3/4] fix(windows): retry transient atomic replace denial --- src/paths.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/paths.rs b/src/paths.rs index 060e000..010d0b9 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -163,10 +163,38 @@ pub fn atomic_write_io(filepath: &Path, content: &str) -> std::io::Result<()> { tmp.as_file().sync_all()?; // Persist atomically (temp file → target path via rename) - tmp.persist(filepath).map_err(|e| e.error)?; + persist_temp_file(tmp, filepath)?; Ok(()) } +#[cfg(not(windows))] +fn persist_temp_file(tmp: tempfile::NamedTempFile, filepath: &Path) -> std::io::Result<()> { + tmp.persist(filepath).map(|_| ()).map_err(|e| e.error) +} + +#[cfg(windows)] +fn persist_temp_file(mut tmp: tempfile::NamedTempFile, filepath: &Path) -> std::io::Result<()> { + // MoveFileExW can transiently return ERROR_ACCESS_DENIED while antivirus, + // indexing, or another reader briefly holds the destination. Preserve the + // same temp file and retry the atomic replacement for a short bounded + // window instead of failing a config update immediately. + const MAX_ATTEMPTS: u64 = 6; + for attempt in 1..=MAX_ATTEMPTS { + match tmp.persist(filepath) { + Ok(_) => return Ok(()), + Err(err) + if err.error.kind() == std::io::ErrorKind::PermissionDenied + && attempt < MAX_ATTEMPTS => + { + tmp = err.file; + std::thread::sleep(std::time::Duration::from_millis(10 * attempt)); + } + Err(err) => return Err(err.error), + } + } + unreachable!("persist loop returns on success or final error") +} + /// Write content to file atomically (temp file + rename). /// Returns true on success, false on failure. pub fn atomic_write(filepath: &Path, content: &str) -> bool { From 62c16ca119db367d670ec56b3022af0ccfc9a7ce Mon Sep 17 00:00:00 2001 From: aannoo Date: Mon, 6 Jul 2026 19:36:50 +0100 Subject: [PATCH 4/4] test(claude): preseed global onboarding state --- tests/support/claude_mock.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/support/claude_mock.rs b/tests/support/claude_mock.rs index 08a21c2..4781d8d 100644 --- a/tests/support/claude_mock.rs +++ b/tests/support/claude_mock.rs @@ -171,6 +171,18 @@ impl ToolCase for ClaudeCase { } fn prepare(&self, h: &Hcom, base_url: &str) { + // Skip Claude's global first-run theme picker. On Windows, hcom's + // synchronous launch can remain inside that picker until its readiness + // timeout, after which the test no longer has an inject endpoint to + // drive it. Workspace trust is project-scoped and still starts fresh, + // so the tests continue to exercise the trust gate required for hooks. + std::fs::write( + h.claude_home.join(".claude.json"), + serde_json::to_vec(&json!({"hasCompletedOnboarding": true})) + .expect("serialize Claude onboarding state"), + ) + .expect("seed Claude onboarding state"); + // Provider routing + isolated config must survive hcom's CI=1 clean-shell // launch rebuild, so they go through the `$HCOM_DIR/env` passthrough. let claude_home = h @@ -219,12 +231,12 @@ impl ToolCase for ClaudeCase { } fn drive_startup(&self, h: &Hcom, name: &str) { - // With onboarding NOT suppressed (no IS_DEMO), a fresh CLAUDE_CONFIG_DIR - // makes Claude surface its theme picker then its workspace-trust dialog, - // which hcom reports as `launch_blocked`. Accepting trust here is what - // lets Claude register hooks at all ("Skipping ... hook execution - - // workspace trust not accepted" otherwise). Drive each surfaced prompt's - // default (theme=dark, trust=Yes) until the empty input prompt appears. + // Global onboarding is pre-seeded in prepare(), but the fresh workspace + // still surfaces its trust dialog, which hcom reports as + // `launch_blocked`. Accepting trust here is what lets Claude register + // hooks at all ("Skipping ... hook execution - workspace trust not + // accepted" otherwise). Keep the theme handling as a compatibility + // fallback for Claude versions that ignore the seeded state. let deadline = Instant::now() + Duration::from_secs(90); let mut last_screen = String::new(); while Instant::now() < deadline {