Remove unavailable MCP placeholder tool backfill (#22439)

## Why

`UnavailableDummyTools` kept synthetic placeholder tools alive for
historical tool calls whose backing MCP tool was no longer available.
That path adds stale model-visible tool specs and special routing at the
point where unavailable MCP calls should use ordinary current-tool
handling. This removes the runtime backfill instead of preserving a
second compatibility lane.

## Is it safe to remove?

The unavailable tools were added in #17853 after a CS issue when a
previously-called MCP tool failed to load and was omitted from the CS
spec. Now that we have tool search, I think this is resolved:
- API merges tools from previous TST output into effective tool set so
theyre always in CS spec
- if an MCP tool surfaced by TST later becomes unavailable, the model
can still call it and it will just return model-visible error
- both TST output and function call output are dropped on compaction so
model will not remember old calls to MCP post compaction

## What changed

- Delete unavailable-tool collection, placeholder handler, router/spec
plumbing, and obsolete placeholder coverage.
- Keep `features.unavailable_dummy_tools` as a removed no-op feature
tombstone so existing configs still parse cleanly.
- Add an integration-style `tool_search` regression test showing that a
deferred MCP tool surfaced through `tool_search` still routes through
MCP and returns a model-visible tool-call error rather than `unsupported
call`.

## Verification

- `cargo test -p codex-core tool_search`
This commit is contained in:
sayan-oai
2026-05-12 23:30:13 -07:00
committed by GitHub
parent 2630a6ca35
commit 2304ec45ca
14 changed files with 161 additions and 502 deletions

View File

@@ -542,7 +542,6 @@ fn test_tool_runtime(session: Arc<Session>, turn_context: Arc<TurnContext>) -> T
crate::tools::router::ToolRouterParams {
mcp_tools: None,
deferred_mcp_tools: None,
unavailable_called_tools: Vec::new(),
discoverable_tools: None,
extension_tool_bundles: Vec::new(),
dynamic_tools: turn_context.dynamic_tools.as_slice(),
@@ -8607,7 +8606,6 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
crate::tools::router::ToolRouterParams {
deferred_mcp_tools,
mcp_tools: Some(tools),
unavailable_called_tools: Vec::new(),
discoverable_tools: None,
extension_tool_bundles: Vec::new(),
dynamic_tools: turn_context.dynamic_tools.as_slice(),

View File

@@ -58,7 +58,6 @@ use crate::tools::router::ToolRouterParams;
use crate::tools::router::extension_tool_bundles;
use crate::turn_diff_tracker::TurnDiffTracker;
use crate::turn_timing::record_turn_ttft_metric;
use crate::unavailable_tool::collect_unavailable_called_tools;
use crate::util::backoff;
use crate::util::error_or_panic;
use codex_analytics::AppInvocation;
@@ -1245,27 +1244,11 @@ pub(crate) async fn built_tools(
);
let mcp_tools = has_mcp_servers.then_some(mcp_tool_exposure.direct_tools);
let deferred_mcp_tools = mcp_tool_exposure.deferred_tools;
let unavailable_called_tools = if turn_context
.config
.features
.enabled(Feature::UnavailableDummyTools)
{
let exposed_tool_names = mcp_tools
.iter()
.chain(deferred_mcp_tools.iter())
.flat_map(|tools| tools.iter().map(codex_mcp::ToolInfo::canonical_tool_name))
.collect::<HashSet<_>>();
collect_unavailable_called_tools(input, &exposed_tool_names)
} else {
Vec::new()
};
Ok(Arc::new(ToolRouter::from_config(
&turn_context.tools_config,
ToolRouterParams {
mcp_tools,
deferred_mcp_tools,
unavailable_called_tools,
discoverable_tools,
extension_tool_bundles: extension_tool_bundles(sess),
dynamic_tools: turn_context.dynamic_tools.as_slice(),