Skip to content

fix(cli,sdk,api): parallel tool-result pairing, terminal-scope path limit, credential hint - #1

Closed
c01dsummer wants to merge 10 commits into
mainfrom
fix/roadmap-835-838
Closed

c01dsummer wants to merge 10 commits into
mainfrom
fix/roadmap-835-838

Conversation

@c01dsummer

Copy link
Copy Markdown
Owner

Summary

Four defects found while investigating a live claw REPL failure, plus the ROADMAP
entries that record them. They are combined here because each one touches the
append-only tail of ROADMAP.md, so separate PRs would conflict on every merge.

  1. fix(cli) — parallel tool-call results were one message too late. convert_messages
    emitted one wire message per tool result, so a turn with two tool calls produced
    assistant[tool_use, tool_use], user[tool_result], user[tool_result]. The Anthropic
    Messages API requires every tool_use to be answered in the single message that follows
    it, so the request was rejected. Runs of tool results now merge into that one message.
    (ROADMAP Claude vibe coding vibe marketing交流群 + ultraworkers/claw-code#835)
  2. fix(sdk) — terminal-scope record paths exceeded the filesystem path limit. A maximal
    512-byte key expanded to 1024 hex characters and the generated path ran past the 1024-byte
    APFS total-path limit, so set/get failed with File name too long. Records are now
    named from a fixed-width SHA-256 digest; the full key still lives in the record and is
    still checked on read. (ROADMAP https://github.com/e10nMa2k/cc-mini ultraworkers/claw-code#836)
  3. fix(api) — the missing-credential error ignored a configured non-Anthropic endpoint.
    With ANTHROPIC_BASE_URL pointing at a third-party gateway the message named two env vars,
    which reads as "go get an Anthropic key" and never mentioned the endpoint the user had
    configured. A new hint branch names the endpoint and explains the variable-name convention.
    (ROADMAP 怎么全都是中文issue xs ultraworkers/claw-code#838)
  4. docs(runtime) — recorded the MCP stdio fixture flake (ROADMAP 为什么还在持续commit? ultraworkers/claw-code#837, KNOWN FLAKE, not
    fixed): the #[cfg(test)] 200ms/300ms handshake budgets are spent on python3 cold start
    when a dozen fixture children start at once. The budgets stay short on purpose, so the
    cause is documented where the numbers are read instead.
  5. chore — .DS_Store added to .gitignore.

Anti-slop triage

  • Classification: actionable-fix

  • Evidence: the CLI defect reproduces from this repository's own dogfood session
    (.claw/sessions/e49619b247aa3fce/session-1792170907799-0.jsonl), whose step-1 assistant
    message carries two tool_use blocks followed by two separate tool-result messages:

    api returned 400 Bad Request (invalid_request_error): messages.3: `tool_use` ids were
    found without `tool_result` blocks immediately after:
    call_01_4QJqcZ0075kla1ILwra75582
    

    The SDK defect reproduces deterministically with a 512-byte key:

    read terminal scope '…/6b6b…(1024 hex chars)….json': File name too long (os error 63)
    

    The API defect reproduces with claw prompt "say hello" and
    ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic and no credential exported.

  • Non-destructive review result: merge candidate

Verification

  • Both new regression assertions fail against the pre-fix code. The CLI one was verified
    by extracting main with git archive into a separate tree, rebuilding there, and running the
    new integration test, which reported the exact payload behind the live 400:

    request 1: ["user[text]", "assistant[tool_use, tool_use]", "user[tool_result]", "user[tool_result]"]
      left:  ["toolu_multi_read"]
      right: ["toolu_multi_read", "toolu_multi_grep"]
    
  • Every file on this branch is byte-identical to the branch it was cherry-picked from
    (git diff per file against fix/parallel-tool-result-pairing-835,
    fix/terminal-scope-path-length-836, docs/roadmap-835-837,
    fix/anthropic-credential-hint-base-url).

  • cargo test --workspace -- --test-threads=1 → 1803 passed, 0 failed, 1 ignored.
    The ignored case is api/tests/client_integration.rs::live_stream_smoke_test, marked
    requires ANTHROPIC_API_KEY and network access.

  • cargo test -p api -p sdk -p rusty-claude-cli on this branch → all targets pass
    (api 172, sdk 119 + 12 + 109, cli 224 + 109 + 13 + 11 + 8 + 1 + 1).

  • cargo test --workspace (default parallel) still reports 10-11 runtime MCP fixture
    failures: that is the pre-existing flake recorded in ROADMAP 为什么还在持续commit? ultraworkers/claw-code#837, not a regression — see
    the serial baseline above.

  • scripts/fmt.sh --check; scripts/roadmap-check-ids.sh;
    python .github/scripts/check_doc_source_of_truth.py;
    python .github/scripts/check_release_readiness.py; python -m unittest discover -s tests.

  • cargo clippy --workspace --all-targets -- -D warnings reports no findings in the changed
    crates; the pre-existing findings in runtime/trident.rs, runtime/file_ops.rs,
    runtime/line_range_selector.rs, runtime/compact.rs and claw-rag-service are untouched.

  • No live secrets, tokens, or private logs are included. A credential appeared in the local
    conversation log during dogfooding and is not part of this diff (verified by grepping the
    whole tree for it).

Resolution gate

Finder metadata kept showing up as an untracked file in this macOS checkout.

(cherry picked from commit 868a8ac)
Drives the real `claw` binary against the mock Anthropic service for the
`multi_tool_turn_roundtrip` scenario and asserts on the payload the provider
would receive: every `tool_use` of an assistant message must be answered by
`tool_result` blocks in the message immediately after it, and a turn with two
tool calls must still produce exactly one tool-result message.

This lives in `tests/` rather than beside `convert_messages` so it can be
reverted independently of the fix. Run against the pre-fix implementation it
fails with the payload that caused the live 400:

  request 1: ["user[text]", "assistant[tool_use, tool_use]",
              "user[tool_result]", "user[tool_result]"]
  left:  ["toolu_multi_read"]
  right: ["toolu_multi_read", "toolu_multi_grep"]

The mock service does not validate pairing itself, which is why the contract is
asserted here. `cargo test -p rusty-claude-cli --test
parallel_tool_result_pairing` passes with the fix and fails without it.

(cherry picked from commit 4eba1e2)
`convert_messages` emitted one wire message per `MessageRole::Tool` record.
The Anthropic Messages API requires every `tool_use` of an assistant message to
be answered by `tool_result` blocks in the single message that follows it, so
the second and later results of a parallel tool-call turn landed one message
too late and the provider rejected the whole request:

  api returned 400 Bad Request (invalid_request_error): messages.3: `tool_use`
  ids were found without `tool_result` blocks immediately after:
  call_01_4QJqcZ0075kla1ILwra75582

Single-call turns were unaffected, which is why the defect only surfaced when
the model requested several tools at once. Evidence: `.claw/sessions/
e49619b247aa3fce/session-1792170907799-0.jsonl`, whose step-1 assistant message
carries two `tool_use` blocks followed by two separate tool-result messages.

Fix: `convert_messages` is split into a per-message `convert_message` plus a
folding accumulator `absorb_tool_result_message`, which merges a run of
adjacent tool-result messages into the one `user` message that follows the
assistant turn. The merge only extends a message whose content is tool results
and nothing else, so user text and assistant turns can never be joined.
`converts_tool_roundtrip_messages` keeps its previous single-call contract.

Verification: `cargo test -p rusty-claude-cli` (224 unit tests plus the
integration targets); `scripts/fmt.sh --check`.

(cherry picked from commit 1c0ba66)
… fix

Records the reported symptom, the root cause, the fix, and the verification that
fails against the pre-fix code. Id 835 is the next free id from
`scripts/roadmap-next-id.sh`.

(cherry picked from commit 9af3c77)
`TerminalScopeStore::record_path` 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 ENAMETOOLONG:

  read terminal scope '…/6b6b…(1024 hex chars)….json': 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 shrinking the
chunk 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 the
`cargo test --workspace` job on ubuntu-latest could not catch it.

Fix: records are named from a fixed-width SHA-256 digest (`<32 hex>.json`) and
the multi-level chunking is gone. 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.
`sha2` joins the crate's dependencies, reusing the version `runtime` already
pulls, so `Cargo.lock` gains one line.

The regression test 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.

Verification: `cargo test -p sdk` (119 unit tests, 12 + 109 integration);
`scripts/fmt.sh --check`; the new assertion fails against the pre-fix code.

(cherry picked from commit 07f020f)
Records the symptom, why the errno pointed at a harmless-looking leaf name, why
CI could not see it, and the verification that fails against the pre-fix code.

(cherry picked from commit 1a66aad)
The `#[cfg(test)]` budgets (200ms/300ms against production 10s/30s) are
deliberately short so the scripted stdio fixtures finish quickly, but they are
also why the default parallel `cargo test --workspace` reports failures in
`mcp_stdio::tests` and `mcp_tool_bridge::tests`: the fixtures are real `python3`
children (cold start measured at 18-21ms here), and a dozen of them starting at
once spends the budget on process startup alone.

The comment records the shape of the failure, the `--test-threads=1` workaround,
and why the budgets stay short, so the next reader does not misread a scheduling
artifact as a product defect.

(cherry picked from commit f058e84)
…around

KNOWN FLAKE (not fixed): records the cause, the measured numbers, why only the
two in-crate unit-test modules go red, and the deliberate decision to keep the
budgets short rather than widen them for parallel tolerance.

This branch also carries pinpoints 835 and 836, which document fixes landing on
their own branches: the roadmap is append-only, so the three entries are filed
together to keep ROADMAP.md touched by a single PR.

(cherry picked from commit 7cb2482)
…al error

`claw prompt "say hello"` with `ANTHROPIC_BASE_URL=https://api.deepseek.com/
anthropic` and no credential exported reported:

  missing Anthropic credentials; export ANTHROPIC_AUTH_TOKEN or
  ANTHROPIC_API_KEY before calling the Anthropic API

Truthful but misleading. Third-party Anthropic-compatible endpoints accept the
`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` names purely as a wire-compatibility
convention — the value has to be a credential the endpoint itself issued, and an
`sk-ant-` key is rejected — so the message reads as "obtain an Anthropic key"
while never mentioning the endpoint the user had already configured.

`anthropic_missing_credentials_hint()` sniffed only `FOREIGN_PROVIDER_ENV_VARS`
and ignored `ANTHROPIC_BASE_URL`, so users who had already pointed claw at a
gateway got the generic wall.

Fix: a `custom_anthropic_base_url_hint()` branch fires whenever
`ANTHROPIC_BASE_URL` differs from `anthropic::DEFAULT_BASE_URL` (empty and
whitespace-only values are ignored), names the configured URL, and explains the
variable-name convention. It runs before the foreign-provider loop: 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. Base-url resolution reuses
`anthropic::read_base_url()` so the hint cannot disagree with the client's own
endpoint.

Seven pre-existing hint/error tests depended on the ambient `ANTHROPIC_BASE_URL`
and failed under a shell that sets it; they now pin the variable explicitly.

Verification: `cargo test -p api` (172 unit tests plus four integration
targets); `scripts/fmt.sh --check`; the exact failing command now reports the
endpoint it was configured with.

(cherry picked from commit ae07657)
Pre-registered as 838 because branches for 835/836/837 are already open against
the same append-only section.

(cherry picked from commit ff347ce)
@c01dsummer

Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR ultraworkers#3360 — the review target is the upstream repo, not this fork.

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.

1 participant