Skip to content

feat(claude): opt-in ENABLE_TOOL_SEARCH injection for launched Claude Code sessions - #5029

Merged
lidge-jun merged 1 commit into
devfrom
codex/4838-claude-tool-search
Sep 18, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/4838-claude-tool-search

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

ocx claude now supports claudeCode.toolSearch, injected as ENABLE_TOOL_SEARCH into the launched Claude Code environment. It is opt-in and off by default, which is a deliberate departure from the issue's request — the evidence below is why.

The reported problem is real and the named variable is the right lever. Claude Code 2.1.273 disables MCP tool deferral on a non-first-party ANTHROPIC_BASE_URL, keyed on the host rather than the model, so it hits every ocx claude session. Its own diagnostic states both the gate and the precondition for lifting it:

[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=... is not a first-party Anthropic host. Set ENABLE_TOOL_SEARCH=true (or auto / auto:N) if your proxy forwards tool_reference blocks.

and its settings documentation:

"Enable it only if your endpoint forwards and accepts the request shape it will receive; when it does not, requests fail with HTTP 400."

We do not forward those blocks on a translated route, so injecting it unconditionally would not do what it appears to do. Deferral is a server-side context optimisation, not a smaller request. Claude Code's own deferred-tool predicate requires input_schema to still be present alongside defer_loading: true, so every schema stays on the wire and it is Anthropic's API that keeps them out of the model's context and answers the tool_search server tool with tool_reference blocks. On the translated path:

  • src/claude/inbound-content-options.ts toolsToResponses converts any tool carrying input_schema into a full Responses function tool and ignores defer_loading entirely; the tool_search_tool_* server tool falls through to the drop branch.
  • src/claude/compatibility.ts lists tool_search, tool_reference and deferred_tools in the table whose true entries mean unsupportedcompatible is computed as !FEATURE_CODES.some(code => detected.has(code) && FEATURES[code]). The issue reads that table as declaring support; it declares the opposite.

So on a routed non-Anthropic model the provider would still receive all 292 schemas, while Claude Code would believe its context was nearly empty and stop compacting — trading a session that compacts often for one that overruns the provider window. An operator who has set claudeCode.compatibility: "enforce" gets a hard 400 on every request instead. On the native Anthropic passthrough route the body reaches Anthropic untouched and the feature works exactly as reported.

Shipping it as a documented, upgrade-safe switch gives the reporter their shell alias as first-class configuration today, and keeps the default honest until translated deferral is actually implemented.

Behaviour:

  • claudeCode.toolSearch: true injects "true"; a string is passed through verbatim so Claude Code's own vocabulary (auto, auto:N, force) stays reachable.
  • false, absent and blank inject nothing rather than injecting "false", which would be the one path that could disagree with an operator's own export.
  • All three injection surfaces use their existing user-wins primitive, so an exported ENABLE_TOOL_SEARCH always wins: setDefault in ocx claude, injectLever in the launchd system env, and a conditional export in the shell env file.
  • ENABLE_TOOL_SEARCH is deliberately not added to NATIVE_STRIPPED_LEVERS, and the list now carries a comment saying why: it is the only one of those levers whose value is meaningful to a native session, where the base URL is first-party and the variable is the user's own tuning knob.

Out of scope, deliberately: no Dashboard control (gui/src/pages/claude-manual-env.ts untouched, so this PR changes no gui/ file), and no management-API field. PUT /api/claude-code builds its next block with { ...(config.claudeCode ?? {}) }, so a hand-written key survives a Dashboard save.

Verification

  • No local verification was run. This lane is forbidden from running any local suite, focused test, typecheck, build, install, or the ocx binary, because an earlier local run deleted a real ~/.opencodex directory. Hosted CI on Linux, Windows and macOS is the verification path for this change.
  • Static review instead: the new helper lives in src/claude/context-windows.ts next to resolveAutoContext, which all three call sites already import, so no server-to-cli import edge is introduced. deleteUntrustedAnthropicSlots only touches ANTHROPIC_PARENT_ENV_SLOTS, so an inherited ENABLE_TOOL_SEARCH reaches setDefault intact.
  • The mechanism was established against the installed Claude Code 2.1.273 binary rather than documentation alone: the gate function, the auto/auto:N/force parser, the deferred-tool predicate requiring input_schema, and the tool-search-tool-2025-10-19 beta value were all read out of it.
  • Added tests/claude-integration/claude-cli.test.ts > "ocx claude tool-search deferral (ocx claude should set ENABLE_TOOL_SEARCH=true: Claude Code disables MCP tool deferral behind a non-Anthropic base URL, so every tool schema ships in full #4838)": default-off, the three accepted value forms, the false/blank/absent cases, the operator-export-wins case, and the native-fallback case asserting this lever survives while CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY is shed. That file is 803 lines and carries no tests/fixtures/file-size-baseline.json entry, so the ratchet is not involved.
  • Docs: the English guide gains the env-table row and a troubleshooting entry explaining the passthrough/translated split; the seven translated guides gain the matching table row in their own language so no locale contradicts the English source.

Closes #4838

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. This PR adds one non-secret environment variable and touches no credential path; the auth-mode and admission-token logic around the insertion point is unchanged.

… Code sessions

Claude Code turns MCP tool deferral off whenever ANTHROPIC_BASE_URL names a
non-first-party host, keyed on the host rather than the model, so every ocx claude
session inlines all MCP tool schemas. Add claudeCode.toolSearch, injected as
ENABLE_TOOL_SEARCH by the three launch surfaces that already carry the other
Claude Code levers, with user-wins semantics throughout.

Default off. Deferral is a server-side optimisation, not a smaller request: deferred
tools still carry input_schema on the wire, and Anthropic's API is what keeps their
schemas out of the model context and answers the tool_search server tool. A
translated route never reaches that machinery, so enabling it there leaves the
provider holding every schema while Claude Code stops accounting for them.

Closes #4838
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 18, 2026 06:38
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 7 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ca617083-2d83-42d7-827f-1176309d2f58

📥 Commits

Reviewing files that changed from the base of the PR and between baae905 and 6d810da.

📒 Files selected for processing (14)
  • docs-site/src/content/docs/fr/guides/claude-code.md
  • docs-site/src/content/docs/guides/claude-code.md
  • docs-site/src/content/docs/ja/guides/claude-code.md
  • docs-site/src/content/docs/ko/guides/claude-code.md
  • docs-site/src/content/docs/ru/guides/claude-code.md
  • docs-site/src/content/docs/tr/guides/claude-code.md
  • docs-site/src/content/docs/zh-cn/guides/claude-code.md
  • docs-site/src/content/docs/zh-tw/guides/claude-code.md
  • src/claude/context-windows.ts
  • src/cli/claude.ts
  • src/server/system-env-shell.ts
  • src/server/system-env.ts
  • src/types/config.ts
  • tests/claude-integration/claude-cli.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-18T06:42:13.248108Z 6d810da PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 76 / 80

지금 dev 끝은 baae9057b (#5023 catalog contextWindow 백필)이고, 그 아래 #5021 unsupportedHostedTools · #5020 vi locale · #4781 native-main 프로필이 깔려 있다. 이 PR은 그 방향과 겹치지 않는 Claude 런치 레버다. 이슈 #4838이 말한 증상은 진짜다. Claude Code 2.1.273은 ANTHROPIC_BASE_URL이 1st-party Anthropic 호스트가 아니면 MCP tool deferral을 끈다. 호스트 기준이라 ocx claude로 띄운 세션은 모델이 무엇이든 전부 걸린다. 리포터가 말한 것처럼 커넥터 292개면 턴 1에 MCP 스키마만 248k 토큰이 실리고, /context가 이미 창을 넘긴다. 셸에서 ENABLE_TOOL_SEARCH=true ocx claude 하면 당장 줄어드니 레버 이름도 맞다.

그런데 이슈가 원한 “기본 ON / 무조건 주입”은 지금 dev 번역 경로에선 위험하다. src/claude/compatibility.ts의 FEATURES에서 tool_search · tool_reference · deferred_toolstrue인 쪽은 “지원한다”가 아니라 “지원 못 한다(호환성 위반)”다. compatible!FEATURE_CODES.some(code => detected.has(code) && FEATURES[code])로 계산된다. 이슈가 표를 반대로 읽었다. 또 src/claude/inbound-content-options.tstoolsToResponsesinput_schema가 있으면 전부 Responses function tool로 펼치고 defer_loading은 보지 않으며, tool_search_tool_* 서버 툴은 drop 분기로 빠진다. deferral은 요청을 작게 만드는 게 아니라 Anthropic 쪽이 컨텍스트에서 스키마를 빼 주는 서버 최적화다. 번역 경로에 켜면 프로바이더는 스키마 292개를 그대로 받고, Claude Code만 “거의 비었다”고 믿어 compact를 멈춘다. claudeCode.compatibility: "enforce"면 매 요청 400이다. 네이티브 Anthropic 패스스루에서만 본문이 그대로 가서 이 기능이 의미 있다. 그래서 기본 OFF · opt-in claudeCode.toolSearch로 간 판단이 맞다. 이슈 요청을 그대로 따르지 않은 이유가 PR 본문에 증거로 적혀 있다.

구현은 세 주입면이 같은 헬퍼를 쓴다. claudeToolSearchEnvsrc/claude/context-windows.ts에 두고, ocx claudesetDefault, launchd injectLever, 셸 env 파일의 conditional이 모두 그걸 탄다. true"true", 문자열은 trim 후 그대로(Claude Code의 auto / auto:N / force 어휘 유지), false · 없음 · 공백은 주입 자체를 안 한다("false"를 넣지 않음). setDefaultundefined/빈 문자열이면 early return이라 기본 OFF가 안전하다. 사용자가 이미 export한 값은 세 경로 모두 user-wins. ENABLE_TOOL_SEARCHNATIVE_STRIPPED_LEVERS에 넣지 않았고, 네이티브 세션에선 1st-party라서 그 변수가 사용자 튜닝 노브라는 주석도 맞다. MANAGED_SYSTEM_ENV_NAMES에는 넣어서 launchd 정리 대상이 된다. GUI/gui/src/pages/claude-manual-env.ts는 안 건드렸고, PUT /api/claude-code{ ...(config.claudeCode ?? {}) }로 다음 블록을 만들므로 손수 쓴 키는 대시보드 저장에도 살아남는다. 영문 가이드에 패스스루/번역 갈라놓기 트러블슈팅이 들어가고, fr/ja/ko/ru/tr/zh-cn/zh-tw 표 행도 맞춰 두었다. 테스트는 tests/claude-integration/claude-cli.test.ts에 기본 OFF · 값 형태 · false/blank · operator-wins · native-fallback(게이트웨이 discovery는 벗기고 tool-search는 유지)까지 있다. 로컬 스위트는 안 돌렸다고 했고(이전 ~/.opencodex 삭제 사고), hosted CI가 검증 경로다.

라인 - 이게 무슨 문제다

tests/claude-integration/claude-cli.test.ts (toolSearch: "auto:25") - PR 본문·영문 가이드·헬퍼 주석은 Claude Code 파서가 N을 최소 100으로 거부한다고 적혀 있다. 와이어에 그대로 넣는 동작 검증이라면 통과하지만, 픽스처 값이 문서의 유효 어휘와 어긋나서 “opencodex가 auto:25를 정상으로 취급한다”처럼 읽힐 수 있다. auto:100 또는 "auto"로 바꾸는 편이 문서와 맞다.

docs-site 번역 가이드(표 행만 추가) - 영문만 “번역 경로에선 켜면 스키마는 그대로·compact만 멈춘다 / enforce면 400” 경고가 있다. 비영문 사용자는 표만 보고 toolSearch: true를 켠 뒤 routed 모델에서 더 나빠질 수 있다. 의도적 범위라면 OK지만, 최소 한 줄 경고 링크나 짧은 주의는 locale마다 같은 무게가 낫다.

src/server/management/agent-settings-routes.ts - 본문 타입에 toolSearch가 없다. PR이 말한 대로 스프레드로 손수 키는 살아남지만, 대시보드가 claudeCode를 다시 쓰면 UI에 안 보이는 키라 운영자가 “저장했는데 왜 없냐”로 헷갈릴 여지는 남는다. 이번 스코프 밖(GUI 미변경)이 맞는지 확인만 하면 된다.

src/claude/context-windows.ts claudeToolSearchEnv - 알 수 없는 문자열("yes", "1")도 trim만 하고 그대로 주입한다. Claude Code가 무시하거나 이상하게 읽을 수 있어도 opencodex가 막지 않는다. 의도(어휘를 여기 재구현하지 않음)면 OK. 막으려면 closed vocabulary validation이 따로 필요하다.

gui/ · Dashboard - ENABLE_TOOL_SEARCH를 UI에 안 올린 것은 번역 경로 함정을 기본 UX에 실지 않으려는 선택으로 보인다. 다만 #4838 리포터처럼 “문서/설정에 문자열이 없어서 못 찾았다”는 불만은 영문 트러블슈팅으로 거의 해소되고, 대시보드 토글은 여전히 없다.

메인테이너의 판단이 필요한 지점

  • #4838은 “기본 ON”을 요청했는데 이 PR은 기본 OFF다. 이슈를 Closes로 닫을지, “opt-in으로 해결·기본 ON은 번역 deferral 이후”로 코멘트하고 닫을지.
  • 번역 가이드에 영문과 같은 함정 경고를 지금 넣을지, 후속 docs PR로 미룰지.
  • Dashboard/PUT /api/claude-codetoolSearch를 노출할지. 지금은 손수 config만.
  • 테스트 픽스처 auto:25를 문서 유효 값으로 고칠지(동작 의미는 동일).

너의 추천
CI(Linux/Windows/macOS) 초록이면 머지. Closes #4838로 이슈 닫고, 이슈에 “기본 ON은 번역 경로가 tool_reference를 실제로 포워드할 때까지 보류·지금은 opt-in” 한 줄만 남기면 된다. 머지 전 여유 있으면 테스트의 auto:25auto:100/"auto"로 바꿔 문서와 맞추라. GUI·관리 API 노출은 후속.

이 댓글은 grok-bot이 작성했습니다

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d810da8a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/types/config.ts
* inject nothing — they do not force the variable off, because a value the
* operator exported themselves always wins.
*/
toolSearch?: boolean | string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the structure owners for the new config lever

This adds a persisted claudeCode setting and changes the Claude CLI and server environment-injection contracts, but the commit updates none of the applicable documents mapped by structure/INDEX.md (including runtime.md, config.md, and the client-integration owners). Update the owned structure documentation in the same change so the maintainer-facing source of truth describes toolSearch and its three injection surfaces.

AGENTS.md reference: src/AGENTS.md:L10-L11

Useful? React with 👍 / 👎.

Comment thread src/server/system-env.ts
Comment on lines +316 to +317
const toolSearch = claudeToolSearchEnv(config.claudeCode?.toolSearch);
if (toolSearch !== undefined) injectLever("ENABLE_TOOL_SEARCH", toolSearch);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile owned launchctl values when toolSearch changes

On macOS with claudeCode.systemEnv enabled, once opencodex has injected and tracked ENABLE_TOOL_SEARCH, a later injectSystemEnv call sees the existing launchctl value and injectLever treats it as user-owned. Consequently changing toolSearch from true to "auto:N", false, or unset leaves the old value active indefinitely; this also occurs when a tracking record survives a crash/reapply. Recognize tracked keys as opencodex-owned, update or unset this key according to the new config, remove it from injectedKeys when disabled, and cover the transition in tests/server/system-env.test.ts rather than only testing buildClaudeEnv.

AGENTS.md reference: AGENTS.md:L376-L379

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 18, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

Approving the judgement call, and recording why, because this PR does not do what its issue asked for and that is the right outcome.

The issue asked for ENABLE_TOOL_SEARCH=true unconditionally, reading the compatibility table as declaring support. It declares the opposite: src/claude/compatibility.ts lists tool_search, tool_reference and deferred_tools as unsupported, and compatible is computed as "no detected feature is flagged", so a true entry means "would reject". Shipping the requested default on a translated route would have told Claude Code its context was nearly empty — so it would stop compacting — while toolsToResponses kept sending every schema anyway, and under claudeCode.compatibility set to enforce it would have been a hard 400.

That is a failure mode worse than the reported one, and it would have been indistinguishable from a proxy bug to the person who asked for it.

Two details in the investigation are worth keeping. Establishing the gate from the installed Claude Code binary rather than from documentation is what produced the accepted value set (true / auto / auto:N / force) and the host-keyed log line that names the condition exactly. And the observation that deferral is a server-side context optimisation rather than a smaller request — the deferred-tool predicate still requires input_schema next to defer_loading, so all 292 schemas stay on the wire — corrects the premise the issue was reasoning from. Both belong in the closing comment, because a reporter who is told only "opt-in instead of default" learns nothing about why.

Keeping the lever out of NATIVE_STRIPPED_LEVERS with a comment explaining that it is the one lever whose value is meaningful to a first-party native session is the kind of decision that is invisible until someone deletes it, so thank you for writing the reason down next to it.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Merging with macOS legs outstanding, and recording why rather than leaving it implicit.

At this exact head the full Linux suite (test 1/4 through 4/4), gates, storage policy, enforce-target, the docs build, and the keyring and npm-global smokes are green. The macOS legs are queued behind a saturated hosted-runner pool shared by several concurrent lanes, and the sharded macOS legs are separately known to go silent mid-suite and be cancelled at their job budget — a long-standing defect recorded with six occurrences in #4956, including two from the 2.58.0 round that were previously written off as capacity.

This change is platform-neutral, so waiting on a queue that is both saturated and known-unreliable would delay the work without adding information. The evidence that governs the release is not per-PR macOS legs; it is the full-platform lane=all dispatch at the frozen release candidate, which is held until #4956 has a named cause. Nothing is promoted on the strength of this merge.

Stating the boundary plainly: this is merged on Linux, gates and cross-platform smoke evidence at its exact head, with macOS coverage deferred to the candidate run rather than claimed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant