Skip to content

[Bug]: With agent browser/device access off, the agent still sees every preview_* and device_* tool #17202

Description

@alberduris

Note

Written by Claude (Claude Code, Opus 5.5) at my request, and reviewed by me before posting.

Before submitting

  • My Claude searched existing issues and did not find a duplicate.
  • Claude included enough detail to reproduce or investigate the problem, and I checked it.

Area

apps/server

Steps to reproduce

  1. In Settings, turn off "Agent browser access". Leave "Agent device access" off (the default).
  2. Start a new Claude Code thread.
  3. Ask the agent to list its mcp__t3-code__* tools, then to call preview_status and device_list.

Expected behavior

The agent does not see the preview_* and device_* tools. That is what the setting's doc comment says (settings.ts):

Turning this off withholds the MCP credential, so the t3-code server (and with it every preview_* tool) is never attached to a provider session, and the prompt text describing those tools is dropped along with them.

enableAgentDeviceAccess is documented to gate the device_* tools "the same way" (settings.ts).

Actual behavior

The agent sees all 14 preview_* tools and all 4 device_* tools. Calling them fails.

preview_status:

MCP credential does not grant the preview capability: browser preview tools are off for this thread. Do not retry them. To check a page, use a headless browser from the shell, such as Playwright, or curl. The user can turn on "Agent browser access" in Settings; it applies when the agent session next starts.

device_list:

Agent device access is turned off for this environment.

So the setting blocks the calls, but the tools are still offered to the agent.

Impact

Minor bug or occasional failure

Version or commit

0.0.45 (desktop)

Environment

macOS 14 (Darwin 23.6.0), Claude Code 2.1.294

Activity

  1. juliusmarminge commented on Oct 8, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the clear report. I traced this through the code on main (a4c9494). I didn't reproduce it at runtime, but the code matches what you saw: the settings only control which tool calls are allowed. They don't change which tools the agent is offered. The doc comment in settings.ts looks out of date.

    What happens

    • A credential is always issued. ProviderSessionManager.prepareMcpSession gives every thread a credential with orchestration, worktree and pull-requests. It adds preview or device only when the matching setting is on (apps/server/src/orchestration-v2/ProviderSessionManager.ts:485-491, issued at :523-528). So the t3-code server is attached whatever "Agent browser access" is set to.
    • The tool list ignores the credential. /mcp is a single McpServer with every toolkit registered at startup, including layerPreviewToolkit and layerDeviceToolkit (apps/server/src/mcp/McpHttpServer.ts:850-861). The auth middleware resolves the credential and passes it to the request (McpHttpServer.ts:147-180). I couldn't find anything that filters tools/list by it, so every credential appears to get the full list.
    • The block happens at call time. The preview handlers call requireThreadMcpCapability("preview") (apps/server/src/mcp/toolkits/preview/handlers.ts:65, :252). That produces the PreviewAutomationUnavailableError text you quoted (packages/contracts/src/previewAutomation.ts:830). The device tools use requireThreadMcpCapability("device"), which maps to "Agent device access is turned off for this environment." (apps/server/src/mcp/toolkits/device/handlers.ts:70-77). This is a server-wide setting (one per environment), and the capability is fixed when the credential is issued, so the device tools are gated the same way.
    • This isn't specific to Claude. Claude attaches t3-code whenever a session credential exists and pre-approves mcp__t3-code__* (apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:960-988). Codex adds it to mcp_servers without a tool filter (CodexAdapterV2.ts:1336-1347). Pi registers every tool it gets back from tools/list (piT3McpExtensionSource.ts:272-280). So Codex, Pi and the others most likely show the full list too. Only the prompt text is gated: Codex drops the browser and device blocks (apps/server/src/provider/CodexDeveloperInstructions.ts:31-41), and Claude never gets the browser block.

    When it diverged

    #7083 originally withheld the whole credential, so the t3-code server wasn't attached at all, and the settings.ts comment was written for that version. #10839 (merged Sep 9) started issuing a credential every time, for pull-requests, and turned preview into a capability on it. Its own comment says this "withholds the preview tools without taking the server away". The comment at packages/contracts/src/settings.ts:1299-1310 was never updated. A day later, #10677 added enableAgentDeviceAccess with "the same way" wording (settings.ts:1344-1352). By then the gate was already capability-based, so it looks like the device tools were never hidden from the list. The orchestration and thread tools added since then mean the server now has to stay attached anyway.

    Impact

    Calls fail cleanly and tell the agent what to do instead, so the calls themselves appear to stay blocked. The cost is 18 tool definitions in every session with the settings off. There's also some mis-steering: Claude's T3 instructions say "When it exposes preview_* tools, prefer those tools" (apps/server/src/provider/T3OrchestrationInstructions.ts:43), and those tools are listed, so it may try them before falling back.

    Possible fixes (not yet decided)

    1. Filter tools/list (and refuse tools/call) by the credential's capabilities in McpHttpServer. One server-side change would cover every provider and outside MCP clients. I haven't checked whether Effect's McpServer supports filtering the list per request, so this may need a custom list handler or separate toolkit registration.
    2. Hide the tools for each provider instead: for example Claude disallowedTools for mcp__t3-code__preview_* / device_*, and Codex's per-server tool filter. This works but has to be repeated for every adapter.
    3. Either way, update the two settings.ts doc comments to describe the capability model.

    Related

    • #7083: the original withhold feature
    • #10839: moved to the always-issued credential
    • #10677: added the device setting
    • #13559: the actionable preview error text you saw
    • #16482: Claude prompt text for other providers' tools
    • #7870: preview tools dropping out after a restart
    • Discussion #6792

    I didn't find a duplicate issue or an open PR that fixes this.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 8, 2026
  3. jwinpbe commented on Oct 8, 2026

    @jwinpbe

    Thanks for tracing this. I reproduced the catalog mismatch with Codex 0.161.0 through both the authenticated HTTP endpoint and the native Codex API.

    Your first proposed fix is supported by Effect's existing McpSchema.EnabledWhen annotation. I have a local implementation that reads the authenticated invocation scope when tools are discovered and called. It filters the shared catalog without dependency changes or per-provider tool filters. Browser and device groups remain independent, other tools remain available, and the existing handler permission checks stay in place. Calls to hidden tools are refused before reaching their handlers.

    On unchanged main at 12069eefd, both access settings off still produced all 80 tools, including 21 browser tools and four device tools. On local commit f6b2b7fc, based on that revision, HTTP discovery and fresh native Codex conversations agreed:

    • Neither capability: 55 tools.
    • Browser only: 76 tools.
    • Device only: 59 tools.
    • Both: 80 tools.

    The real T3 manager, credential registry, HTTP registration, adapter and native process ran against isolated state. Unrelated domain services were stubbed, and only Codex was tested live. All 32 focused MCP tests passed, along with server typecheck, targeted lint/format and the server bundle build. The test command, from apps/server, was:

    ../../node_modules/.bin/vp test run src/mcp/McpHttpServer.test.ts src/mcp/McpDeviceToolkit.test.ts src/mcp/toolkits/worktree/registration.test.ts src/mcp/McpInvocationContext.test.ts

    I also checked access changes in both directions while attached: the credential and catalog stayed unchanged until idle detach/reopen, which refreshed both while preserving the native conversation and process. This implementation follows the credential's capabilities and preserves that lifecycle behavior. Model-payload token savings remain to be measured.

    I'd like to contribute this fix. Would you accept a PR limited to authenticated catalog filtering, its tests, and corrections to the outdated settings comments? I'll rebase onto current main and repeat the focused checks before opening it. I'll propose broader orchestration-context improvements separately in Ideas.

    Note

    Written with GPT-6.1 Sol (Codex) at my request, and reviewed, edited, and approved by me.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions