Skip to content

/connect shows providers that are disabled in the config #54145

Open

Description

@river-hands-si

Description

In v1, if enabled_providers was set to a restricted list, /connect only showed providers that were actually enabled.
In v2, /connect now shows all providers, even if enabled_providers is set to a restricted list.

This makes it hard for a company to prevent users from using unapproved providers. (In our case, like in #2853, we want to lock down to only a custom provider.)

Plugins

None

OpenCode version

2.0.26

Steps to reproduce

  1. Configure a custom provider, then set enabled_providers to only its ID.
  2. Start OpenCode and run /connect.
  3. Observe that providers outside the allowlist still appear.

Screenshot and/or share link

No response

Operating System

Ubuntu 26.04 (in a Docker container, running inside Ubuntu 26.04 on WSL on Windows 11).

Terminal

No response

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report. I could reproduce this on 2.0.26, and it still happens on the latest v2 branch (5cfe3b4).

    What I did:

    1. Configured a single custom provider (mycompany, via @ai-sdk/openai-compatible) and set enabled_providers: ["mycompany"].
    2. Started OpenCode. The custom model was selected, so the config was loaded.
    3. Ran /connect.

    The Connect an integration dialog still listed OpenCode Console, openai, github-copilot, amazon-bedrock, azure, the Cloudflare providers, digitalocean, gitlab and others, and searching "openai" found it. The recording and screenshots below show it, and the script below reproduces it.

    It looks like enabled_providers is applied to the provider list but not to the list of integrations that /connect shows. I didn't check whether connecting a provider outside the allowlist makes its models usable.

    Related:

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Repro for #54145: /connect lists providers outside `enabled_providers`.
    import { Effect } from "effect"
    import { OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        config: {
          provider: {
            mycompany: {
              npm: "@ai-sdk/openai-compatible",
              name: "My Company Gateway",
              options: { baseURL: "http://127.0.0.1:9/v1" },
              models: { "company-model": { name: "Company Model" } },
            },
          },
          enabled_providers: ["mycompany"],
        } as any,
      },
      ({ ui }) =>
        Effect.gen(function* () {
          yield* Effect.sleep(1500)
          yield* ui.screenshot("01-home")
          yield* ui.type("/connect")
          yield* Effect.sleep(500)
          yield* ui.enter()
          yield* Effect.sleep(1500)
          yield* ui.screenshot("02-connect-dialog")
          yield* ui.type("openai")
          yield* Effect.sleep(1000)
          yield* ui.screenshot("03-connect-search-openai")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. assigned and unassigned on Oct 9, 2026
  3. argszero commented on Oct 9, 2026

    @argszero
    Contributor

    I'd like to work on this.

    I traced where the two lists diverge, and the gap matches your report exactly.

    enabled_providers is normalized into experimental.policies as provider.use allow/deny entries (packages/core/src/config/normalize.ts), and those are then applied in packages/core/src/config/plugin/policy.ts:

    for (const record of catalog.provider.list()) {
      const policy = policies.findLast((policy) => Wildcard.match(record.provider.id, policy.resource))
      if (policy?.effect === "deny") catalog.provider.remove(record.provider.id)
    }

    That transform only visits the provider catalog. The /connect dialog is fed by a different registry — Integration.Service.list() via server.integration integration.list — and nothing applies the policy to it, so every integration keeps showing up regardless of enabled_providers.

    The two registries already line up by id: packages/core/src/config/plugin/provider.ts treats a configured provider's id as its integration id (const integrationID = id) and calls ctx.integration.transform(...), so the same plugin context can also trim the integration registry. The plan is to extend the policy plugin to remove denied integrations the same way it removes denied providers, so a restricted allowlist shows only allowlisted integrations in /connect.

    I have a checkout of v2 at the commit the bot tested, so I can verify the dialog and add a test on the config policy path. If you would rather the integration registry stay untouched and the filtering happen at the server endpoint instead, say so and I will follow that.

  4. argszero commented on Oct 9, 2026

    @argszero
    Contributor

    Opened #54186 for this.

    The fix applies provider policy to the integration registry as well: a provider.use deny now removes that provider's integration from the registry /connect reads, and the integration catalog reloads when config or organization-managed policy changes. Integrations registered under their own id — the OAuth integration a remote MCP server registers, for example — are left to integration.use, so a provider allowlist does not hide them from the dialog.

    Covered by three tests in packages/core/test/config/policy.test.ts: removal under a wildcard deny with one allow, restoration when the deny is lifted, and the reload path. The change also updates specs/v2/provider-policy.md, which documents the hosted:<integration ID> resource of integration.use that nothing implements yet — worth deciding whether that should be the surface for this instead.

  5. rshibanov commented on Oct 20, 2026

    @rshibanov

    Reproduces on 2.1.0 (current npm latest), so this is still present after the latest release.

    Setup: config uses the v2 policies surface (provider.use deny *, allow llama-local and opencode). opencode models correctly lists only the allowed providers, but /connect →
    "Connect an integration" still lists every integration (OpenAI, GitHub Copilot, Bedrock, Azure,
    Cloudflare, DigitalOcean, GitLab, Console, ...). So the provider catalog is filtered, while the
    integration registry behind /api/integration is not.

    The fix for this is already written and verified: #54186 by @argszero. It was closed on 2026-10-20
    for a non-technical reason — GitHub's limit of 3 open PRs for contributors without write access.
    The author wrote: "Closing this to stay within GitHub's 3-open-pull-request limit ... The branch
    remains on my fork, so I can reopen it on request."

    Could a maintainer reopen and merge it (or take over the branch)? The change applies the same
    provider.use statements to the integration registry that /connect reads, and leaves
    integrations registered under their own id (e.g. remote MCP OAuth) governed by integration.use.

    Environment: OpenCode 2.1.0, Ubuntu 24.04 on WSL2.

  6. dennisrutjes-1 commented on Oct 26, 2026

    @dennisrutjes-1

    Still reproduces on 2.1.4 (managed /etc/opencode/opencode.json, Linux).

    Setup: provider.use deny * + allow ai-gateway (a custom AISDK provider, file:///opt/ai-gateway-provider). /connect still lists every integration.

    The "Popular" group should also disappear when only custom providers are allowed. Today it keeps showing "OpenCode Console" (and the other popular providers) even though policy denies them. The Popular category is hard-coded in the TUI (opencode-go, opencode, openai, github-copilot, anthropic, google), so it is not affected by enabled_providers, disabled_providers or provider.use policies. An organisation that locks users to one gateway cannot hide it through config.

    Workaround we use to get most of the way there: negate the built-in plugins individually (-opencode.models.dev, -opencode.websearch.* and 32 -opencode.provider.* entries). Caveats:

    1. -opencode.provider.* also disables opencode.provider.dynamic, which breaks custom AISDK providers (No AISDK provider plugin returned an SDK), so every plugin has to be listed by name.
    2. "OpenCode Console" stays visible anyway. disabled_providers: ["opencode"] and enabled_providers: ["ai-gateway"] don't hide it, and a plugin calling store.remove("opencode") on the integration store seemed to break the session (Model unavailable).

    Questions:

  7. DocMAX commented on Oct 31, 2026

    @DocMAX

    Let AI fix it, what are you waiting for?!

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions