Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ status-help.txt
.envrc.local
result
result-*
.DS_Store
18 changes: 18 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -8018,3 +8018,21 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
834. **DONE — Local-model syntax bypass only covered Ollama-shaped tags, not bare LM Studio/llama.cpp aliases** — follow-up to #833/PR #3213. `is_local_openai_model_syntax()` waived strict `provider/model` syntax under `OPENAI_BASE_URL` only when the model name contained a `.` or `:` (Ollama-tag-shaped). A local server registering a model under a bare alias with none of those characters — for example an LM Studio custom name or a llama.cpp `--alias` value like `mistral` — still failed CLI-parse-time validation unless the user discovered the `local/` prefix workaround. Fix: (1) `is_local_openai_compatible_base_url()` in `openai_compat.rs`, already used to waive the API-key requirement for loopback/private-network base URLs, is now `pub` and re-exported from the `api` crate root; (2) `is_local_openai_model_syntax()` in `rusty-claude-cli`'s `main.rs` calls it against the configured `OPENAI_BASE_URL` and accepts any non-empty model name when the host is local/private, matching the existing `OLLAMA_HOST` bypass; (3) remote/hosted `OPENAI_BASE_URL` gateways keep the stricter dot/colon-tag heuristic so a mistyped bare name doesn't silently route to a paid hosted API; (4) `local/` remains the explicit escape hatch for slash-containing IDs regardless of host; (5) added an LM Studio section and updated routing notes in `docs/local-openai-compatible-providers.md`. [SCOPE: claw-code]

**Verification.** `scripts/fmt.sh --check`; `cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings`; `cargo test --manifest-path rust/Cargo.toml --workspace`; new tests `local_openai_base_url_bypasses_bare_model_names`, `private_network_openai_base_url_bypasses_bare_model_names`, and `remote_openai_base_url_still_requires_provider_model_or_tag_syntax` in `rusty-claude-cli`'s `alias_resolution_tests` module; `is_local_openai_compatible_base_url_is_reachable_via_crate_root` in `api`'s `client.rs` confirming the re-export.

835. **DONE — Parallel tool-call turns sent `tool_result` blocks one message too late** — found while investigating a live `claw` REPL failure that returned `api returned 400 Bad Request (invalid_request_error): messages.3: tool_use ids were found without tool_result blocks immediately after: call_01_4QJqcZ0075kla1ILwra75582`. `runtime` records one `MessageRole::Tool` message per tool call, and `convert_messages()` in `rusty-claude-cli`'s `main.rs` turned each of them into its own wire message. The Anthropic Messages API requires *every* `tool_use` of an assistant message to be answered by `tool_result` blocks **in the single message immediately after it**, so when a turn issued two tool calls at once the first result was paired and the second arrived one message late — the provider rejected the whole request. Single-call turns were unaffected, which is why the defect only showed up when the model requested parallel tools. Fix: (1) `convert_messages()` is split into a per-message `convert_message()` plus a folding accumulator `absorb_tool_result_message()`; (2) a run of adjacent tool-result messages now merges into the one `user` message that follows the assistant turn, matching what the API requires; (3) the merge only extends a message that carries tool results and nothing else, so user text and assistant turns can never be joined; (4) `converts_tool_roundtrip_messages` keeps its previous single-call contract. Evidence for the reported failure: `.claw/sessions/e49619b247aa3fce/session-1792170907799-0.jsonl`, whose step-1 assistant message carries two `tool_use` blocks followed by two separate tool-result messages. [SCOPE: claw-code]

**Verification.** New unit test `converts_parallel_tool_call_results_into_one_paired_message` in `rusty-claude-cli`'s `main.rs` asserts both ids are answered in one message and in call order; new integration test `rust/crates/rusty-claude-cli/tests/parallel_tool_result_pairing.rs` drives the real `claw` binary against the mock Anthropic service for the `multi_tool_turn_roundtrip` scenario and asserts the wire contract on the captured request bodies. Run against the pre-fix implementation that integration test fails with the payload behind the live 400 (`assistant["tool_use", "tool_use"]` followed by `user["tool_result"]`, `user["tool_result"]`; only `toolu_multi_read` answered). It lives in `tests/` rather than unit-test scope so it can be reverted independently of the fix. `scripts/fmt.sh --check`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli`; `cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings` reports no findings in the changed crates.

836. **DONE — Terminal-scope record paths exceeded the filesystem path limit for long correlation keys** — `TerminalScopeStore::record_path()` in the `sdk` crate expanded the correlation key into full hex and chunked it into 180-byte path components. A maximal 512-byte key expands to 1024 hex characters, so the generated path ran past the 1024-byte total-path limit enforced by APFS and `set()`/`get()` failed with `File name too long (os error 63)`. The misleading part is where the limit lives: every component stayed within the 255-byte filename limit (five 180-byte directories plus a 129-byte leaf), so the errno named a generated leaf name the caller never chose, and lowering the chunk size cannot help because the hex expansion stays 1024 characters however it is split. The same code passes on Linux, where `PATH_MAX` is 4096, so `cargo test --workspace` on the ubuntu-latest CI job could not catch it. Fix: (1) `record_path()` now names records from a fixed-width SHA-256 digest (`<32 hex>.json`) and the multi-level chunking is gone; (2) the full key still lives in `TerminalScopeRecord::correlation_key` and is still checked on read, so `encoded_keys_are_isolated_and_cannot_traverse_paths` keeps holding; (3) `encode_key_as_hex()` is replaced by `encode_key_for_path()`; (4) `sha2` joins the `sdk` dependencies, reusing the version `runtime` already pulls so `Cargo.lock` gains one line. [SCOPE: claw-code]

**Verification.** `long_hex_keys_use_safe_path_components` now asserts on the path shape (longest component ≤ 255 bytes, total path < 1024 bytes) instead of only round-tripping, because a round trip inside a short temporary root succeeds even when the generated path is unusable in practice. That assertion fails against the pre-fix implementation with the recorded error above. `cargo test --manifest-path rust/Cargo.toml -p sdk`; `scripts/fmt.sh --check`; `cargo clippy --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings` reports no findings in the changed crates.

837. **KNOWN FLAKE (not fixed) — MCP stdio fixtures exceed their test-only handshake budget under the default parallel test run** — found while establishing a green baseline for #835/#836. `cargo test --workspace` (the command [rust-ci.yml](.github/workflows/rust-ci.yml) runs, with no thread limit) reports 10-11 failures in `runtime`'s `mcp_stdio::tests` and `mcp_tool_bridge::tests` on this machine, all of the shape `Timeout { server_name: "alpha", method: "initialize", timeout_ms: 200 }`. Cause: `mcp_stdio.rs` halves the production handshake budgets for test builds only — `MCP_INITIALIZE_TIMEOUT_MS` 200ms vs 10_000ms, `MCP_LIST_TOOLS_TIMEOUT_MS` 300ms vs 30_000ms — while the tests drive a real `python3` stdio fixture whose cold start measures 18-21ms (`min=18 max=21 avg=20` over 10 runs). That is ~10x headroom when the tests run serially, but the default run starts a dozen fixture children at once and the budget is consumed by process startup alone. The `#[cfg(test)]` gate is why the impact is bounded: integration targets such as `runtime/tests/*.rs` link the non-test library and therefore use the production budgets, which is why only the two in-crate unit-test modules go red — no production path is affected, and no CLI or harness behavior depends on these values. [SCOPE: claw-code]

**Disposition.** The budget constants were deliberately left unchanged: shortening them is what makes a real handshake regression fail fast, so widening them to buy parallel tolerance trades away the guard those cases exist for. Instead the cause is documented where the numbers are read, and this item records it for claws that hit the failures and misread them as a product defect. This mirrors the existing documented-but-unfixed items in this roadmap (for example the `clippy` job running without `-D warnings`).

**How to read it.** Re-run with `--test-threads=1` before treating those failures as a regression. On the same machine and commit: `cargo test --workspace` (parallel) = 11 failed / 1792 passed; `cargo test --workspace -- --test-threads=1` = **0 failed / 1803 passed / 1 ignored**; `cargo test -p runtime --lib -- --test-threads=1` = 721 passed; `cargo test -p runtime --lib mcp_stdio:: -- --test-threads=1` = 22 passed. The failure set also moves between runs (`mcp_tool_bridge::tests::call_tool_payload_structure` and `manager_times_out_slow_tool_calls` each fail in one run and pass in the next), and libtest aborts in-flight cases when one panics, so the reported `passed` count itself varies between parallel runs — another sign that these are scheduling artifacts rather than deterministic defects. The single `ignored` case is `api/tests/client_integration.rs::live_stream_smoke_test`, which is marked `requires ANTHROPIC_API_KEY and network access`.

838. **DONE — Missing-credential message ignored a configured non-Anthropic `ANTHROPIC_BASE_URL`** — dogfooded while running `./target/debug/claw prompt "say hello"` with `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic` and no credential exported. The error was truthful but misleading: `missing Anthropic credentials; export ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY before calling the Anthropic API` names two env vars that a DeepSeek user does need to set, yet reads as "go obtain an Anthropic key" and never mentions the endpoint the user had already configured. Third-party Anthropic-compatible endpoints (DeepSeek's `/anthropic` path, corporate gateways, local proxies) accept those two variable names only as a wire-compatibility convention: the value must be a credential the endpoint itself issued, and an `sk-ant-` key will be rejected. `anthropic_missing_credentials_hint()` only sniffed `FOREIGN_PROVIDER_ENV_VARS` (`OPENAI_API_KEY` / `XAI_API_KEY` / `DASHSCOPE_API_KEY`) and never looked at `ANTHROPIC_BASE_URL`, so this whole class of user — someone who already knows what to configure — fell through to the generic wall. Fix: (1) a new `custom_anthropic_base_url_hint()` branch returns a hint whenever `ANTHROPIC_BASE_URL` is set to anything other than `anthropic::DEFAULT_BASE_URL` (empty or whitespace-only values are ignored); (2) the hint names the configured URL, states it is not the Anthropic API, and explains the variable-name convention; (3) the branch runs before the foreign-provider loop, because a configured endpoint is a fact about what the user asked for while a presence check on another provider's key only guesses at intent; (4) base-url resolution reuses `anthropic::read_base_url()` so the hint can never disagree with the client's own endpoint. [SCOPE: claw-code]

**Verification.** Reproduced the exact command with the fix: the error now carries a third line naming `https://api.deepseek.com/anthropic`. New tests `anthropic_missing_credentials_hint_names_a_custom_base_url` (asserts the URL, the "not the Anthropic API" claim, and that both credential variable names survive), `anthropic_missing_credentials_hint_prefers_custom_base_url_over_foreign_creds` (pins the precedence rule against a regression, since both hints can apply at once), and `anthropic_missing_credentials_hint_is_none_for_the_default_base_url`. Seven pre-existing hint/error tests previously depended on the ambient `ANTHROPIC_BASE_URL` and now pin it explicitly, so they no longer pass or fail according to the developer's shell. `cargo test -p api` (172 unit tests plus the four integration targets); `scripts/fmt.sh --check`.
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading