release: Codewhale v0.9.4 release train - #5135
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 35796438 | Triggered | JSON Web Token | 918aa8c | crates/lane/src/control.rs | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Reviewing PR #5135 (v0.9.4 release train)
Todo list
|
Reviewing PR #5135 (v0.9.4 release train)
Todo list
|
|
Claude finished @Hmbown's task in 10m 30s —— View job Reviewing PR #5135 (v0.9.4 release train)Todo list
SummaryLarge but disciplined release train (219 files, +23355/-8848 vs BlockingSilent Anthropic-dialect regression via The new "one vendor row" alias-collapse design moves legacy dialect strings ( Non-blocking
Nits
Security / secrets
Good design worth calling out
Could not run |
| "deepseekcn", | ||
| "deepseek-china", | ||
| // Dialect is wire=anthropic on this provider, not a second catalog row. | ||
| "deepseek-anthropic", |
There was a problem hiding this comment.
Blocking — silently drops the user's explicit Anthropic-dialect selection via codewhale config set provider / CODEWHALE_PROVIDER.
ProviderKind::parse() (provider_kind.rs:257) does .find() over PROVIDER_REGISTRY in order and returns the first provider whose id() or aliases() matches. Since Deepseek (this struct) is registered before DeepseekAnthropic, and "deepseek-anthropic"/"deepseek_anthropic"/"deepseek-claude"/"deepseek_claude" were moved here from DeepseekAnthropic::aliases() (now &[] at line 662), ProviderKind::parse("deepseek-anthropic") now resolves to ProviderKind::Deepseek, never DeepseekAnthropic.
That matters because the CLI/env path (ConfigToml::set_value("provider", …) in config/src/lib.rs:2497-2508, used by codewhale config set provider deepseek-anthropic — cli/src/lib.rs:3684 — and by CODEWHALE_PROVIDER=deepseek-anthropic, lib.rs:6355-6357) only does self.provider = provider; it never sets the wire field. wire_prefers_anthropic() (lib.rs:3828) only returns true for the literal DeepseekAnthropic/MinimaxAnthropic/ModelstudioTokenPlanAnthropic/ModelstudioCodingPlanAnthropic kinds or an explicit wire = "anthropic" config value — neither is set here. So a user who previously ran codewhale config set provider deepseek-anthropic (or set the env var) got the Anthropic Messages endpoint; after this change they silently get the default DeepSeek (Chat Completions) endpoint instead, with no error or warning. Same issue applies to mini-max-anthropic/mini_max_anthropic on MinimaxAnthropic and all the modelstudio-*-anthropic/modelstudio-coding-plan* aliases collapsed onto ModelstudioTokenPlan.
Note the TOML config-file path (toml::from_str("provider = \"deepseek-anthropic\"")) is not affected — that uses the separate #[serde(alias = …)] attributes on the ProviderKind enum (provider_kind.rs:22-27), which still correctly map to DeepseekAnthropic. Only the Provider::aliases()-driven ProviderKind::parse() path used by the CLI config set command and env var regressed.
The new test deepseek_anthropic_aliases_collapse_onto_primary_with_wire_toggle (config/src/tests.rs:4321) asserts the collapsed parse() result as intended design, and separately tests wire = "anthropic" via TOML and the legacy ProviderKind::DeepseekAnthropic enum value directly — but never exercises set_value("provider", "deepseek-anthropic") end-to-end through resolve_runtime_options, so this regression has no test coverage either way.
Suggested fix: have set_value's "provider" branch (and the CODEWHALE_PROVIDER env parsing) additionally derive and persist wire/mode when the matched alias belongs to a legacy dialect/plan kind, or keep ProviderKind::parse resolving those specific alias strings to the dialect kind and translate to wire at the ConfigToml → resolve_runtime_options boundary instead of at parse time.
| // fingerprint (the item's JSON, matching what append_message | ||
| // stores as content) against the persisted chain and against | ||
| // items already appended in this loop. | ||
| let mut seen: HashSet<String> = self |
There was a problem hiding this comment.
Non-blocking: resume-history dedup uses a HashSet<String> content fingerprint rather than a multiset/positional check. Legitimately repeated identical history items (e.g. two separate identical no-arg tool calls, or repeated short user replies) can be silently dropped instead of appended, on top of the intended (and correctly working) idempotent-resume case. Worth a follow-up if repeated-identical-turns are a real occurrence in practice.
| aliases: ["telecom-js", "telecom_js", "telecomjs-cn", "tokenhub"] | ||
| ); | ||
|
|
||
| /// Alibaba Cloud Model Studio — Token Plan (OpenAI-compatible Chat Completions). |
There was a problem hiding this comment.
Nit / reuse: ModelstudioTokenPlan and ModelstudioCodingPlan don't override wire_policy() (both default to Fixed(ChatCompletions), same as the provider! macro's default), yet are hand-written impl Provider blocks duplicating the same boilerplate the macro already generates for NvidiaNim, Telecomjs, etc. Only the two *Anthropic variants need a hand-written impl (to override wire_policy), matching the existing DeepseekAnthropic/MinimaxAnthropic pattern — consider using the provider! macro for the two base variants.
The one-way ownership ratchet failed the release train on the v0.9.4 Fleet work. Reviewed and re-baselined: - aggregate owned Rust 662389 -> 668365 - three new 1000-line modules admitted: fleet/store.rs (1114, the named Fleet store v2), tui/transcript.rs (1062, the release-quality transcript pass), views/fleet_detail.rs (1056, the secondary named-Fleet editor behind /fleet fleets) Also tightened rather than left slack: seven modules have since split below the threshold (cli/cloud.rs, client/responses.rs, session_control_acceptance.rs, skills/system.rs, tui/footer_ui.rs, widgets/footer.rs, widgets/header.rs) and are dropped from the allowlist, so the count lands at 168 and the largest module at 19087. All three new modules are recorded as v0.9.5 split candidates. Verified: scripts/check-source-structure-budget.py -> PASS with no "can tighten" remainder.
|
Claude finished @Hmbown's task in 1m 17s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewThree small commits landed on top of
Blocking issue: still fixed, untouched by this batch
Previously-flagged itemsUnchanged and not touched by this batch — resume-history dedup ( Net assessmentThis synchronize is pure CI/hygiene housekeeping (a Windows-compile fix, a formatting pass, and a reviewed structure-budget update) — no new findings, no logic changed. The train's substantive state is unchanged since the last content review.
|
The #5123 fail-closed check keyed on `agent_type_explicit`, which is set by both `type` and `role`. That rejected every read-only spawn that names a role, and broke four tests plus the whole read-only Workflow leaf path: spawn rejected: builder implies write capability; write_authority=read_only is a contradiction. `role` and `type` are not the same claim: - `role: "release_lead"` is a roster id. It is copied into `profile` only as a lookup key and the member is not resolved until `apply_spawn_profile`, so at validation time the agent type is still the default Worker and the role says nothing about write capability. - `role: "implementer"` is a type alias, but still an identity. A Fleet role and its authority posture are independent — an acceptance workflow resolves `implementer` to its saved profile while narrowing that child to the read-only tool set, which is what `leaf_allowed_tools` enforces. - `type: "builder"` *is* a capability claim, and pairing it with read_only is the #5123 lie: a child labeled builder holding only recon tools, which self-BLOCKs after burning a turn. So the guard now keys on a new `agent_type_named` (the caller wrote `type`), leaving `agent_type_explicit` alone for profile-conflict resolution. The Workflow driver correspondingly stops emitting a write-capable `type` on a read_only leaf that already carries a role/profile — the role resolves the identity and the redundant type was the only thing making the leaf look like a write claim. Contract change worth naming: `type: "implementer"` + read_only now fails where it used to parse. That spelling is an alias of `builder`, so the older "implementers can be narrowed" contract and #5123 asserted opposite verdicts on the same role. Narrowing is preserved through `role`; the two tests that covered it now use `role`, with their assertions unchanged. Verified: the 4 tests #5123 broke plus 3 new/updated ones pass (builder_or_worker_plus_read_only_authority_fails_closed, roster_role_plus_read_only_authority_still_spawns, read_only_roles_reject_write_authority_but_implementers_can_be_narrowed, declared_read_only_write_roles_derive_without_mutating_shell, terminal_blocked_gate_fails_workflow_finalization, stopship_acceptance_fixture_emits_role_gate_and_terminal_receipts). cargo fmt + cargo clippy --workspace --all-targets -D warnings clean.
The heading said 2026-08-04; the release is cut on 2026-08-05. Also record what the spawn contract actually landed as: `type` is the capability claim that can contradict write_authority, `role` is an identity that may still be narrowed to read-only, and callers who wrote `type: "implementer"` for that narrowing need `role: "implementer"`. Verified: web tests 250/250 (they assert a dated section), and scripts/release/generate-release-body.sh 0.9.4 renders.
|
Claude finished @Hmbown's task in 1m 53s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewTwo commits landed on top of
Verified the fix directly
No correctness, provider/route-safety, or security concerns in either commit — this is a well-diagnosed, well-tested tightening of a fail-closed check that had over-fired, not a loosening of it. Blocking issue: still fixed, untouched by this batchNeither commit touches Previously-flagged non-blocking items and nitsUnchanged and not touched by this batch — resume-history dedup ( Net assessmentThis synchronize is a small, well-scoped fix to the #5123 guard (it had over-fired on
|
The README body already said v0.9.4 while the three eval examples still passed --harness.version 0.9.1, which would resolve a two-release-old runtime companion set.
scripts/sync-changelog.sh regenerates crates/tui/CHANGELOG.md from the root file; the refined #5123 wording had only landed in the root, which failed the Version drift gate.
|
Claude finished @Hmbown's task in 50s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewTwo small docs-only commits landed on top of
Both are pure documentation/changelog sync — zero Rust/TypeScript source touched (confirmed via Blocking issue: still fixed, untouched by this batch
Previously-flagged non-blocking items and nitsUnchanged and not touched by this batch — resume-history dedup ( Net assessmentThis synchronize is pure documentation/version-string bookkeeping — no code changed, no new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
· branch |
`refresh_system_prompt_is_noop_when_unchanged` failed once in a full `cargo test --workspace` run and passed in isolation. The composed stable prompt reads ambient process state, and this test was the odd one out among the 38 env-sensitive tests in the file: without `lock_test_env()` a concurrent test mutating the environment between the two refreshes shifts the hash, so the no-op assertion fails for reasons unrelated to the code under test. Not a product regression — a test-isolation defect that made the release CI nondeterministic.
|
Claude finished @Hmbown's task in 48s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Verified directly: Blocking issue: still fixed, untouched by this batch
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single, well-diagnosed test-flake fix — no production code changed, no new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|
The earlier re-baseline was measured before the role/type spawn fix, its regression tests, and the env-lock line landed, so the ratchet caught this lane's own growth: 668365 -> 668400. Verified: scripts/check-source-structure-budget.py -> PASS.
|
Claude finished @Hmbown's task in 40s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Pure numeric bookkeeping — no logic, tests, or behavior touched. Both source commits being accounted for ( Blocking issue: still fixed, untouched by this commit
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single-line budget-ceiling correction with no code, logic, or test changes — no new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|
Third and last conflict from the #5123 fail-closed rule. The release QA contract `release_four_read_only_fleet_roles_launch_with_canonical_prompts` launches worker, scout, reviewer, and verifier — it calls that set "the four canonical read-only Fleet roles" — and the worker stopped launching at all (`worker did not launch once: 0 != 1`). Worker is the unnamed default role; it renders as `general` and takes its capability from `write_authority`, not from its name, so a read-only worker is an ordinary general-purpose child. Builder is the role that actually claims write capability, and it is the one the #5123 transcript shows self-BLOCKING — the worker in that same transcript ran fine. So the check is now exactly: caller wrote `type`, that type resolves to Builder (either spelling), and authority is read_only. Everything else — `type=worker` + read_only, any `role` + read_only — stays legal. Verified: release_four_read_only_fleet_roles_launch_with_canonical_prompts passes; builder_plus_read_only_authority_fails_closed covers both the `builder` and `implementer` spellings; new read_only_worker_is_an_ordinary_general_child pins the canonical case; the workflow, roster-role, and narrowing contracts still pass (8 total). cargo fmt clean; structure ceiling folded to 668406.
|
Claude finished @Hmbown's task in 1m 9s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Verified the fix directly
This is a correctness fix, not a loosening of the guard in any unsafe direction — Blocking issue: still fixed, untouched by this commit
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single, well-diagnosed, well-tested correctness fix that completes the #5123 fail-closed narrowing (three iterations total: role vs. type, then this worker-vs-builder split). No new findings. The train's overall state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|
`Test (windows-latest)` failed `codewhale-telemetry` `golden_payload_v1` with a diff that was nothing but \r\n vs \n. The golden is pulled in with include_str!() and compared byte for byte against serde_json::to_string_pretty output, which always emits LF, so the `* text=auto` default corrupted it on a Windows checkout. This file already documents that hazard for the prompt assets; the newer include_str!() inputs (telemetry golden, bundled catalogs and skills under crates/*/assets, the locale packs, the workflow scripts) had never been added. The others survived only because they are parsed or substring-matched rather than compared exactly. `git add --renormalize .` reports no content change: every one of these is already stored with LF, so this only affects what Windows checks out.
|
Claude finished @Hmbown's task in 1m 14s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Verified directly
Blocking issue: still fixed, untouched by this commit
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single, well-diagnosed, correctly-scoped Windows CI fix (
|
Records the three cross-platform regressions blocking the release, the credit-gate fix Hunter approved, and the verified state of everything already landed, so the next agent does not re-derive it. Also names the two traps that hid the regressions: a piped cargo test reports grep's exit code rather than the suite's, and the 2026-08-04 release evidence called the macOS paste failure 'pre-existing on base' when the base was the train rather than main.
|
Claude finished @Hmbown's task in 27s —— View job Claude finished @Hmbown's task in 1m —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Blocking issue: still fixed, untouched by this commit
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single docs/ops handoff-note commit — no code changed, no new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|
ff97641 moved composer wrapping (wrap_text) from hard margin breaks to word-boundary breaks, but cursor_row_col kept recomputing the cursor row with the old margin model, so the two desynchronised: a long paste landed one row short of its marker (paste_matrix_lands_in_the_composer_without_ autosubmitting, timed out at 6s) and the caret drifted behind fast typing. Derive the cursor (row, col) from the SAME wrapped lines the renderer draws so a desync is impossible by construction; the end-of-full-line wrap convention falls out of wrap_text's trailing empty line. Bisected to ff97641 (parent bb0be84 good). Verified: paste test ok in 3.50s, plus cursor_basic_ascii / at_wrap_boundary / with_cjk / cjk_at_wrap_boundary / and_wrap_consistency / composer_wrap_boundary.
The monolithic async start_turn holds two full Config clones plus several
records, the Op::SendMessage, and the inlined ensure_engine_loaded
sub-future (which builds a full EngineConfig), all across ~8 sequential
.awaits. On Windows the runtime thread stack is ~1 MiB (vs ~8 MiB on
Linux/macOS), so this frame overflowed it: start_turn_accepts_dynamic_
tools_and_environment_id aborted the whole test binary (0xc00000fd
STATUS_STACK_OVERFLOW), masking other Windows failures.
Box::pin(async move { ... }).await heap-allocates the whole state machine;
behavior- and Send-preserving, protects every caller. Verified no macOS
regression (start_turn test passes).
safety_summary deliberately reports "<policy> requested, not enforced" on platforms with no OS sandbox (Windows), the honest behavior from the 2026-08-04 audit. The test asserted the enforced string, which only appears when a sandbox backend is present, so it failed on Windows. Branch the assertions on app.sandbox_backend.is_none() (the same signal safety_summary uses) so the test holds on both enforced and unenforced platforms.
The 2026-08-04 evidence called the paste failure "pre-existing on base", but that base was the v0.9.4 train (the rebuild candidate sat on top of it), NOT main. The paste failure passes on main and is a regression against the last released line. Root-caused 2026-08-05 by git bisect to ff97641 and fixed. Name which base; do not let "pre-existing" stand without it.
When `[providers.xai]` selects OAuth and points `oauth_credential_generation` at a Codewhale-owned credential file that no longer exists, `credentials_valid` returns false and cannot fall through (a configured generation is authoritative). So `needs_api_key` stayed true and the generic provider picker reopened on EVERY launch — the dogfood bricked state (#5032). Detect that specific corrupted state, best-effort clear the stale pointer from the persisted config, and surface a truthful xAI-specific re-auth message. The repair never blocks or aborts launch; after it the state is the normal "needs auth", not a bricked loop. Leaves `auth_mode = "oauth"` intact. - `owned_generation_is_dangling(config)`: detector for the missing-file state. - `clear_dangling_xai_oauth_generation(path)`: best-effort persisted repair. - init.rs: detect + repair in the launch path, set an xAI status message, keep the in-memory route consistent with the repaired file. - regression test mirroring `activation_recovers_from_a_dangling_generation_pointer`. Verified: cargo check -p codewhale-tui; cargo test xai_oauth::tests (38 passed).
Re-ships the Fleet/operate constitution; existing users (whose setup_state records 0.8.67) re-acknowledge it once on next launch. Point the prompts regression test at the const so it tracks the version instead of hardcoding it.
cf00a21 to
e44c60d
Compare
|
Claude finished @Hmbown's task in 1m 19s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewSix commits landed on top of
No correctness, provider/route-safety, or security issues found in this batch — all six are small, well-diagnosed, and either directly tested or mechanically verified against their stated cause. Blocking issue: still fixed, untouched by this batch
Previously-flagged non-blocking items and nitsUnchanged and not touched by this batch — resume-history dedup ( Net assessmentThis synchronize is a tight, well-tested batch fixing a real Windows-only regression pair (composer cursor desync + stack overflow), an xAI OAuth-recovery bug (#5032), a platform-aware test correction, and honest doc/version bookkeeping. No new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|
…fixes The v0.9.4 must-fix items (composer cursor/wrap unification, Windows start_turn Box::pin, the #5032 dangling xAI OAuth repair + its test, the platform-aware /status safety-policy test, the 0.9.4 checkpoint bump) grew owned Rust by +201 lines (668406 -> 668607), tripping the one-way aggregate ratchet. No new 1000-line modules; growth is in existing files. Re-baseline the ceiling and record why; pay the aggregate back down in v0.9.5.
|
Claude finished @Hmbown's task in 35s —— View job Reviewing PR #5135 (v0.9.4 release train) — re-review after synchronizeTodo list
What changed since the last reviewOne new commit landed on top of
Blocking issue: still fixed, untouched by this commit
Previously-flagged non-blocking items and nitsUnchanged and not touched by this commit — resume-history dedup ( Net assessmentThis synchronize is a single-commit budget-ceiling correction with no code, logic, or test changes — no new findings. The train's substantive state is unchanged since the last content review: the originally-blocking provider/wire issue remains fixed with solid regression coverage.
|

What this is
The v0.9.4 integration train. Supersedes #5044 (its head
92128e66is an ancestor of this branch — all of the 2026-08-01 source candidate is contained here).Currently 77 commits ahead of
main: the 18 train commits on6860a40eb556(see FINISH-0.9.4.md §0 in codewhale-ops) plus the main-line work they build on.Landing strategy
Per-concern stacked PR chains land on this branch (never directly on main), per docs/RELEASE_QUEUE.md:
Each stack PR carries its own gates + receipts. This train PR merges to main only when the release gates in docs/RELEASE_CHECKLIST.md are all green.
Gates so far
cargo test -p codewhale-tui --bin codewhale-tuibaseline: 9589 pass / 2 fail (pre-existingconfig::credential_scope_tests::*only)cargo clippy --workspace --all-targets --all-features --locked -- -D warningsclean at82b6c27d6cargo check --workspace --all-targets --lockedPASScargo test --workspace --all-features --locked: pending (tracked in the 0.9.4 program ledger)Nothing here is tagged or released; 0.9.4 has not shipped.
No-Issue: release train container; each stacked PR closes its own issue.