Follow-ups from #941: two dispatch_name call sites, silent MCP name collisions, and complete_simple still dropping in-band errors

Open
#972 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
30/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend, cli

Research direction

Start with src/cli/commands.rs:2549, crates/jcode-app-core/src/server/debug_command_exec.rs:327, Registry::register, create_mcp_tools, and complete_simple. Review the two named branches and the existing dispatch-name and stream-event tests before running the relevant crate checks. Done means normalized call sites agree with registration, collisions are diagnosed, and in-band stream errors no longer produce an empty successful response.

Written by the indexing model from the issue text.

Description

autonomous: no bug triage: needs-decision

Three loose ends from #941 (which closed #936 and #937). All three are still present on master at c4cdc67. Filing as one issue since both source threads are now closed.

Branches, since PR creation is restricted to collaborators here:

  • fix/mcp-dispatch-name-call-sitescompare (items 1 and 2)
  • fix/complete-simple-inband-errorscompare (item 3)

1. Two call sites still build the dispatch name by hand

dispatch_name normalizes - to _ in both segments, which the test in #941 pins (hyphenated-server -> mcp__hyphenated_server__). Two places still use the old format! and so no longer match what is registered.

src/cli/commands.rs:2549 — the cold-cache MCP startup wait:

let prefix = format!("mcp__{}__", server);
names.iter().any(|name| name.starts_with(&prefix))

For a server named my-server, registered tools are mcp__my_server__*, so covered never becomes true. The loop cannot detect readiness, burns the full budget, and then logs

jcode run: timed out waiting for cold-cache MCP server(s); their tools may be missing from this run

for tools that did in fact register. Silent latency plus a misleading warning.

crates/jcode-app-core/src/server/debug_command_exec.rs:327mcp:call:<server>:<tool>:

let tool_name = format!("mcp__{}__{}", server, tool);

Misses for a hyphen in either segment, which is the exact case #941 set out to fix.

Both are one-line swaps to dispatch_name. I also added a test pinning dispatch_name(server, "") against the full name, so a prefix scan cannot drift from the registered form again.

2. Dispatch-name collisions are silent

Because normalization is many-to-one, two distinct remote tools can collapse onto one dispatch name:

  • one server exposing both query-docs and query_docs
  • two servers named a-b and a_b

Registry::register is tools.insert(name, tool), so the second registration silently replaces the first. The losing tool stays listed by mcp list but is not the tool that runs — a wrong-tool-executes failure with no diagnostic.

Rare, but bad enough to be worth surfacing. My branch detects duplicates in create_mcp_tools and logs a warning naming the collision. It deliberately does not rename, since the registered name is what maps back to the server's real tool; the point is to make the conflict visible, not to guess a winner.

3. complete_simple still drops in-band stream errors

This is the root cause behind #937's empty smoke output:, and it is unchanged:

match event {
    Ok(StreamEvent::TextDelta(text)) => result.push_str(&text),
    Ok(_) => {}                      // <- StreamEvent::Error lands here
    Err(err) => return Err(err),
}

#941 fixed the Grok runtime to send Err instead, which resolves that report. But the in-band variant is still a legal way to report a failed turn, and other runtimes still use it. Concretely, jcode-provider-claude-cli-runtime converts to Err only when is_retryable_error matches and no output has been seen yet; anything outside that set, such as a stream-event parse failure from handle_output, is forwarded as Ok(StreamEvent::Error) and reaches this same path. Result: Ok(""), and the caller reports "response did not contain X" with nothing after it.

The current contract is effectively "return Err, and also never use the error variant if a caller might be complete_simple", which is easy to violate silently and gives no compile-time signal. Handling the variant here makes the guarantee hold whichever convention a runtime picks. Partial text is kept as evidence of how far the turn got.


Verification. cargo check passes for jcode-base --lib, jcode-app-core --lib, jcode --lib --bins, and jcode-provider-core; cargo fmt clean on all touched crates. I could not run the test binaries for these crates on this machine — security software deletes freshly linked executables on launch (Access is denied, os error 5), and the jcode-base test build exceeds my timeout — so the 2 tests added compile but I have not watched them pass. Flagging that rather than implying otherwise; CI will confirm.

Happy to split this into three separate issues if you would rather track them independently.

Dominant language
Rust
Stars
19.9k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
30

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from 1jehuang/jcode

All issues in 1jehuang/jcode

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.