Skip to content

chore(release): prepare v0.8.45#2118

Merged
Hmbown merged 6 commits into
mainfrom
work/v0.8.45-release
May 25, 2026
Merged

chore(release): prepare v0.8.45#2118
Hmbown merged 6 commits into
mainfrom
work/v0.8.45-release

Conversation

@Hmbown
Copy link
Copy Markdown
Owner

@Hmbown Hmbown commented May 25, 2026

Summary

Prepare CodeWhale v0.8.45 as a focused maintenance release: RLM session objects, cancellable directory/search tools, deterministic whale-species agent names, the /balance scaffold, contributor credit updates, config/runtime hardening, palette audit cleanup, and release metadata sync. Voice input and its hotkey are intentionally deferred to v0.8.46.

Changes since v0.8.44

Security

No Security changelog entries in this release.

Contributor thanks

Thanks @gaord, @zlh124, and @reidliu41 for the harvested fixes and reports credited in this release.

Local release checklist

  • ./scripts/release/check-versions.sh — passed (workspace=0.8.45, npm=0.8.45, lockfile in sync)
  • cargo fmt --all -- --check — passed
  • cargo check --workspace --all-targets --locked — passed
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings — passed
  • cargo test --workspace --all-features --locked — passed (codewhale-tui: 3342 passed; 0 failed; 3 ignored; all integration/doc tests green)
  • ./scripts/release/publish-crates.sh dry-run — passed; higher-level crates verified package contents and correctly wait for internal 0.8.45 dependencies to be published first
  • cargo build --release --locked -p codewhale-cli -p codewhale-tui — passed
  • ./target/release/codewhale --versioncodewhale 0.8.45 (b9822129968e)
  • ./target/release/codewhale-tui --versioncodewhale-tui 0.8.45 (b9822129968e)
  • node scripts/release/npm-wrapper-smoke.js — passed
  • npm --prefix web run lint — passed
  • Release surface search for Goal mode, /mode goal, voice input names, and voice hotkeys — no matches outside ignored build/cache dirs

Known issues

None documented for v0.8.45.

Greptile Summary

This is the v0.8.45 release preparation PR, bundling a range of fixes and feature additions: path-traversal hardening in the skill installer, bearer-token authentication for the HTTP app-server, a tightened project-config merge that strips credential/endpoint/auth overrides from repo-local files, voice-input deferral to v0.8.46, and minor UX additions (cache-savings footer chip, cumulative_turn_secs persistence).

  • Skills path-traversal hardening (install.rs): uninstall, trust, and update_with_registry now validate the skill name via validate_skill_name_segment and resolve symlinks via fs::canonicalize before operating, closing symlink-escape vectors; the install path is unaffected because tarball names are independently validated through parse_frontmatter_name.
  • App-server auth (app-server/src/lib.rs): All HTTP routes (except /healthz) are now behind a require_app_server_token middleware; an auto-generated cwapp_* UUID token is emitted to stderr on first start, and --insecure-no-auth is blocked on non-loopback binds.
  • Project-config security (config/src/lib.rs): merge_project_overrides now uses merge_project_provider_config (model-only) instead of the old merge_provider_config (api_key + base_url + model), preventing a repo .codewhale/config.toml from redirecting API traffic to an attacker-controlled endpoint.

Confidence Score: 4/5

Safe to merge with known open items from prior review rounds; the new code is well-structured and the security improvements are sound.

The skill-installer path-traversal fix, app-server bearer-token auth, and restricted project-config merging are all correct and well-tested. The sandbox mode rank collision between external-sandbox and danger-full-access (both rank 0) noted in a prior review remains unaddressed in this PR, meaning a repo-local config can still silently loosen a user's external-sandbox posture. No new logic errors were found in the features introduced here.

crates/config/src/lib.rs (sandbox_mode_rank / project_sandbox_mode_is_allowed — unresolved from prior review); crates/app-server/src/lib.rs (token comparison — unresolved from prior review)

Important Files Changed

Filename Overview
crates/tui/src/skills/install.rs Path-traversal hardening: validate_skill_name_segment + ensure_target_within_skills_dir added to uninstall/trust/update_with_registry; install path relies on parse_frontmatter_name validation; new unit tests including Unix symlink escape test.
crates/app-server/src/lib.rs Bearer-token auth middleware added for all HTTP routes; token comparison uses plain equality (timing side-channel, flagged in previous review); insecure-no-auth guard rejects non-loopback binds; CORS layer unchanged.
crates/config/src/lib.rs merge_project_overrides now strips api_key/base_url/auth/network/skills from repo-local config; sandbox_mode rank collision between external-sandbox and danger-full-access (rank 0 each) flagged in prior review remains unaddressed.
crates/tui/src/pricing.rs New calculate_cache_savings helper computes miss-rate minus hit-rate savings; caller guards against negative/zero results before display; correct use of Option chaining.
crates/tui/src/session_manager.rs Adds cumulative_turn_secs field to SessionMetadata with serde(default) for backward compatibility; all struct literal sites updated.
crates/tui/src/tui/footer_ui.rs Voice-input status label and active-state guard removed; cache-savings chip appended to footer cost spans with saved > 0.0 guard preventing negative display.
crates/tui/src/tui/voice_input.rs Entire file deleted; voice input deferred to v0.8.46; all call sites, tests, settings, and command-palette entries cleaned up.
crates/tui/src/settings.rs Voice input settings (command, timeout) removed consistently from struct, Default, normalization, env-var loading, set(), display(), and config-view helpers.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CORSLayer
    participant AuthMiddleware as require_app_server_token
    participant Handler
    participant AppState

    Client->>CORSLayer: HTTP request
    CORSLayer->>AuthMiddleware: pass (origin allowed)
    AuthMiddleware->>AppState: read auth_token

    alt /healthz (unprotected)
        AuthMiddleware->>Handler: bypass auth
        Handler-->>Client: 200 ok
    else No token configured (insecure-no-auth loopback only)
        AuthMiddleware->>Handler: bypass auth
        Handler-->>Client: response
    else Bearer token present and matches
        AuthMiddleware->>Handler: authorized
        Handler-->>Client: response
    else Token missing or wrong
        AuthMiddleware-->>Client: 401 Unauthorized
    end

    note over AuthMiddleware: token == expected (plain equality)
Loading

Comments Outside Diff (1)

  1. crates/app-server/src/lib.rs, line 504-509 (link)

    P2 security Non-constant-time bearer token comparison

    token == expected short-circuits on the first differing byte, which is a textbook timing side-channel. An attacker who can send requests to a non-loopback-bound server and measure response latency could incrementally recover the token. For the common loopback case the risk is low, but the server explicitly allows non-loopback authenticated binds, and tokens can be long-lived when supplied via --auth-token or the env var. A constant-time comparison (e.g. subtle::ConstantTimeEq, or comparing HMAC tags of both values with the same key) would close the gap.

    Fix in Devin

Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Copilot AI review requested due to automatic review settings May 25, 2026 09:46
Hmbown added 2 commits May 25, 2026 04:47
Replace the sequential-spawn-index whale-nickname system with a
deterministic hash-based naming scheme that maps each agent ID to a
stable whale species name. The same agent ID always gets the same
friendly name — even across session restarts for persisted agents.

- whale_name_for_id(id): hash agent ID → WHALE_NICKNAMES index
- assign_unique_whale_name(id, active_names): deterministic with
  collision avoidance, appends numeric suffix when base name is taken
- Expand WHALE_NICKNAMES from 25 to ~45 Cetacea species including
  baleen whales, toothed whales, and select dolphins (Delphinidae);
  porpoises excluded as labels that don't carry well
- SubAgent::new now accepts a pre-generated id parameter so the
  spawn method can hash it before construction
- SubAgentsView popup now shows friendly nickname next to raw agent
  ID (dimmed) instead of hiding it
- live_subagent_result accepts optional nickname parameter
- whale_nickname_for_index kept as legacy public API for test snapshots

137 sub-agent tests pass. Taxonomy source: Society for Marine
Mammalogy (2025).
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the project to version 0.8.45, introducing OpenAI model registry updates, bearer token authentication for the app server, and stricter project-level configuration overrides. UI improvements include a refreshed 'Whale' dark palette, a /balance command scaffold, cache savings displays, and deterministic sub-agent nicknames. Security and stability are enhanced through path validation for skills and the addition of timeouts to file-system tools. Review feedback highlights that background tasks for directory listing and file searching are not signaled to stop upon timeout, potentially leading to orphaned threads and resource exhaustion on large filesystems.

Comment on lines +823 to +854
async fn run_blocking_list_dir<F>(
timeout: Duration,
cancel_token: Option<CancellationToken>,
list_dir: F,
) -> Result<Vec<Value>, ToolError>
where
F: FnOnce() -> Result<Vec<Value>, ToolError> + Send + 'static,
{
if cancel_token
.as_ref()
.is_some_and(CancellationToken::is_cancelled)
{
return Err(list_dir_cancelled());
}

let task = tokio::task::spawn_blocking(list_dir);
let result = match cancel_token {
Some(token) => {
tokio::select! {
biased;
() = token.cancelled() => return Err(list_dir_cancelled()),
result = tokio::time::timeout(timeout, task) => result,
}
}
None => tokio::time::timeout(timeout, task).await,
};

ToolResult::json(&entries).map_err(|e| ToolError::execution_failed(e.to_string()))
let joined = result.map_err(|_| list_dir_timeout(timeout))?;
joined.map_err(|err| {
ToolError::execution_failed(format!("list_dir worker failed before completion: {err}"))
})?
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The spawn_blocking task for directory listing is not signaled to stop when a timeout occurs. While tokio::time::timeout correctly returns an error to the caller, the background thread running the directory walk will continue to execute until it either finishes or the turn's cancel_token is manually triggered by the user. On large or slow filesystems, this can lead to a build-up of orphaned threads. Consider creating a child cancellation token that is explicitly cancelled when the timeout expires to signal the worker to stop immediately.

Comment on lines +131 to +164
async fn run_blocking_file_search<F>(
timeout: Duration,
cancel_token: Option<CancellationToken>,
search: F,
) -> Result<Vec<FileSearchMatch>, ToolError>
where
F: FnOnce() -> Result<Vec<FileSearchMatch>, ToolError> + Send + 'static,
{
if cancel_token
.as_ref()
.is_some_and(CancellationToken::is_cancelled)
{
return Err(file_search_cancelled());
}

let task = tokio::task::spawn_blocking(search);
let result = match cancel_token {
Some(token) => {
tokio::select! {
biased;
() = token.cancelled() => return Err(file_search_cancelled()),
result = tokio::time::timeout(timeout, task) => result,
}
}
None => tokio::time::timeout(timeout, task).await,
};

let joined = result.map_err(|_| file_search_timeout(timeout))?;
joined.map_err(|err| {
ToolError::execution_failed(format!(
"file_search worker failed before completion: {err}"
))
})?
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the directory listing tool, the file search worker is not signaled to stop upon timeout. The spawn_blocking task will continue to consume resources in the background even after the TUI has reported a timeout to the user. This is particularly problematic for deep recursive searches on large volumes. The worker should be provided with a cancellation signal that triggers on both user intervention and tool-specific timeout to prevent thread pool exhaustion.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prepare CodeWhale v0.8.45 as a maintenance release, syncing version metadata while landing hardening and UX improvements across the TUI/tools/config/app-server surfaces.

Changes:

  • Release/version bumps and changelog/contributor credit updates for v0.8.45 (Cargo + npm + docs).
  • Tooling/UI improvements: cancellable list_dir/file_search, sub-agent whale nicknames + display updates, cache-savings footer hint, palette/theme audits, /balance scaffold.
  • Hardening: safer project-config overlay rules, skill install path validation, app-server HTTP auth + constrained CORS, OpenAI/Codex auth & model registry updates.

Reviewed changes

Copilot reviewed 46 out of 47 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
web/app/[locale]/faq/page.tsx Updates FAQ copy for /goal behavior.
README.md Updates contributor credits.
npm/deepseek-tui/package.json Bumps npm wrapper version to 0.8.45.
npm/codewhale/package.json Bumps package + binary pin to 0.8.45.
crates/tui/tests/palette_audit.rs Adjusts palette audit assertions to semantic tokens.
crates/tui/src/tui/views/mod.rs Sub-agent list rendering now shows nickname + id.
crates/tui/src/tui/ui/tests.rs Updates session metadata fixtures with new field(s).
crates/tui/src/tui/ui.rs Restores persisted cumulative turn duration on session load.
crates/tui/src/tui/session_picker.rs Updates session metadata fixtures with new field(s).
crates/tui/src/tui/markdown_render.rs Updates test strings to remove current-release wording.
crates/tui/src/tui/footer_ui.rs Adds cache-savings hint in footer cost display.
crates/tui/src/tui/command_palette.rs Adds “Action” section and related filtering/sorting behavior.
crates/tui/src/tui/color_compat.rs Aligns test to palette alias token.
crates/tui/src/tui/app.rs Persists cumulative turn duration; exposes last-turn cache savings.
crates/tui/src/tools/subagent/tests.rs Updates sub-agent constructor usage to accept caller-provided id.
crates/tui/src/tools/subagent/mod.rs Deterministic whale naming + unique assignment; caller-provided agent id.
crates/tui/src/tools/file.rs Makes list_dir cancellable + timeout-protected via blocking worker.
crates/tui/src/tools/file_search.rs Makes file_search cancellable + timeout-protected via blocking worker.
crates/tui/src/theme_qa_audit.rs Adds theme QA audit tests for palette completeness/contrast.
crates/tui/src/skills/install.rs Hardens skill name/path handling to prevent traversal/escape.
crates/tui/src/session_manager.rs Persists cumulative turn duration in session metadata.
crates/tui/src/pricing.rs Adds helper to estimate cache-hit savings.
crates/tui/src/palette.rs Introduces Whale palette tokens, semantic UiTheme fields, updated mappings.
crates/tui/src/models.rs Updates context-window heuristics for more OpenAI model IDs.
crates/tui/src/main.rs Wires theme QA module; tightens project overlay policy handling.
crates/tui/src/localization.rs Adds localized help text for /balance.
crates/tui/src/config.rs Adds OpenAI model list + additional OpenAI provider aliases.
crates/tui/src/commands/mod.rs Registers /balance command and adds tests.
crates/tui/src/commands/balance.rs Adds /balance scaffold behavior.
crates/tui/CHANGELOG.md Adds v0.8.45 changelog entry and compare link.
crates/tui/Cargo.toml Bumps internal crate dependency versions to 0.8.45.
crates/tools/Cargo.toml Bumps protocol dependency version to 0.8.45.
crates/hooks/Cargo.toml Bumps protocol dependency version to 0.8.45.
crates/execpolicy/Cargo.toml Bumps protocol dependency version to 0.8.45.
crates/core/Cargo.toml Bumps internal crate dependency versions to 0.8.45.
crates/config/src/lib.rs Hardens project overlay merge; adds OpenAI OAuth token sourcing + ranks.
crates/config/Cargo.toml Bumps secrets dep + adds serde_json dependency.
crates/cli/src/lib.rs Adds Codex OAuth login/status support; app-server auth/cors flags; env pass-through.
crates/cli/Cargo.toml Bumps internal crate dependency versions to 0.8.45.
crates/app-server/src/main.rs Adds auth token, insecure-no-auth, and CORS origin CLI flags.
crates/app-server/src/lib.rs Requires bearer token for HTTP routes; constrained CORS; redacts config over HTTP.
crates/app-server/Cargo.toml Bumps internal crate versions; adds uuid + dev-deps for tests.
crates/agent/src/lib.rs Expands OpenAI model registry entries + aliasing; adjusts reasoning support flags.
crates/agent/Cargo.toml Bumps config dep version to 0.8.45.
crates/tui/CHANGELOG.md Adds v0.8.45 entry mirroring root changelog.
CHANGELOG.md Adds v0.8.45 entry and compare link.
Cargo.toml Bumps workspace version to 0.8.45.
Cargo.lock Updates lockfile versions and adds new deps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +838 to +842
let task = tokio::task::spawn_blocking(list_dir);
let result = match cancel_token {
Some(token) => {
tokio::select! {
biased;
Comment on lines +146 to +163
let task = tokio::task::spawn_blocking(search);
let result = match cancel_token {
Some(token) => {
tokio::select! {
biased;
() = token.cancelled() => return Err(file_search_cancelled()),
result = tokio::time::timeout(timeout, task) => result,
}
}
None => tokio::time::timeout(timeout, task).await,
};

let joined = result.map_err(|_| file_search_timeout(timeout))?;
joined.map_err(|err| {
ToolError::execution_failed(format!(
"file_search worker failed before completion: {err}"
))
})?
Comment on lines +209 to +215
pub fn whale_name_for_id(id: &str) -> String {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
id.hash(&mut hasher);
let idx = (hasher.finish() as usize) % WHALE_NICKNAMES.len();
WHALE_NICKNAMES[idx].to_string()
}
Comment on lines +229 to +233
// Deterministic suffix from the same hash to keep it stable
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
id.hash(&mut hasher);
let suffix_seed = hasher.finish();
Comment on lines +1868 to +1872
let display_name = agent
.nickname
.as_deref()
.map(|nick| format!("{nick:<12}"))
.unwrap_or_else(|| format!("{id:<12}"));
@Hmbown Hmbown force-pushed the work/v0.8.45-release branch 2 times, most recently from 09b3506 to d9e5446 Compare May 25, 2026 23:08
Comment thread crates/tui/src/palette.rs
Comment thread crates/cli/src/lib.rs
Comment on lines +1299 to +1303
fn app_server_token_from_env() -> Option<String> {
std::env::var("CODEWHALE_APP_SERVER_TOKEN")
.ok()
.or_else(|| std::env::var("DEEPSEEK_APP_SERVER_TOKEN").ok())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 app_server_token_from_env is defined identically in both crates/app-server/src/main.rs and crates/cli/src/lib.rs. If the env-var name or precedence logic needs to change later, both copies will need updating. Consider moving this into a shared function inside codewhale-app-server's public surface so there is a single source of truth.

Suggested change
fn app_server_token_from_env() -> Option<String> {
std::env::var("CODEWHALE_APP_SERVER_TOKEN")
.ok()
.or_else(|| std::env::var("DEEPSEEK_APP_SERVER_TOKEN").ok())
}
fn app_server_token_from_env() -> Option<String> {
// Delegates to the canonical implementation in codewhale-app-server.
codewhale_app_server::app_server_token_from_env()
}

Fix in Devin

…efresh

# Conflicts:
#	crates/config/src/lib.rs
#	crates/tui/src/config.rs
@Hmbown Hmbown force-pushed the work/v0.8.45-release branch from d9e5446 to 49b703f Compare May 25, 2026 23:16
Comment thread crates/config/src/lib.rs
@Hmbown Hmbown merged commit 2283729 into main May 25, 2026
14 checks passed
@Hmbown Hmbown deleted the work/v0.8.45-release branch May 26, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants