Skip to content

[Bug]: ACP registry drops an agent's empty-valued default choice, so GitHub Copilot always runs a custom agent #16898

Description

@leonnardo

Before submitting

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

Area

apps/server

Steps to reproduce

  1. Create any GitHub Copilot CLI custom agent, for example ~/.copilot/agents/example.agent.md:
    ---
    name: example
    description: Example agent.
    ---
    An example agent.
  2. Add GitHub Copilot through the ACP registry (acpRegistry, agent github-copilot-cli, CLI 1.0.93).
  3. Start a new Copilot thread and open the Agent menu in the composer.

Happens with Settings → Plan mode on or off.

Expected behavior

The Agent menu offers Copilot's default agent ("Copilot") next to the custom agents and selects it by default, because that is what Copilot reports. New threads run the default Copilot agent unless the user picks a custom one.

Actual behavior

The Agent menu lists only example, and it is already selected. The default agent can't be picked. Once example is selected, there is no option to switch back to the default Copilot agent. Every new thread sends session/set_config_option agent=example, so the custom agent's instructions and its tools: allowlist apply to every Copilot thread. With a restrictive tools: list, the session loses bash, view, edit and similar tools.

Root cause

Copilot reports the default agent as a choice whose value is an empty string, and says that choice is selected (raw session/new response from copilot --acp --stdio):

{
  "type": "select",
  "id": "agent",
  "name": "Agent",
  "category": "_agent",
  "description": "Select a custom agent persona, or use the default Copilot agent.",
  "currentValue": "",
  "options": [
    { "value": "", "name": "Copilot", "description": "Default Copilot agent" },
    { "value": "example", "name": "example", "description": "Example agent." }
  ]
}

T3 handles it like this:

  1. apps/server/src/provider/acp/AcpSessionConfig.ts: selectChoices passes every choice value through boundedOpaqueValue, which returns undefined for an empty string (value.length > 0). That drops the "" / "Copilot" choice. currentValue: "" is falsy, so the descriptor ends up with no currentValue either.
    Even without that check, the choice couldn't be stored: ProviderOptionChoice.id and SelectProviderOptionDescriptor.currentValue in packages/contracts/src/model.ts are both TrimmedNonEmptyString.
  2. Contributing factor: apps/web/src/providerModels.ts: withoutPlanAgentOption, added in fix(web): hide opencode's plan agent when legacy plan mode is off #6420 for opencode's plan agent, runs on every provider's agent descriptor whenever planModeEnabled is false, which is the default. When there is no currentValue, it falls back to options[0]?.id, which is example here. With plan mode on, this fallback is skipped, but the default choice is still missing and any earlier example selection persists (see 3).
  3. The composer then dispatches agent=example. The ACP adapter calls setConfigOption("agent", "example"), and the live configuration is published back into the provider snapshot (~/.t3/caches/acpRegistry_github_copilot.json now shows "currentValue": "example" with a single option). The composer also keeps agent=example as the last-used selection. This keeps the wrong default for future threads.

Suggested fix

  • Map an empty choice value to a reserved ID (for example __acp_default__) in acpProviderOptionDescriptors, keep a currentValue of "" by mapping it the same way, and turn the reserved ID back into "" before setConfigOption.
  • Limit withoutPlanAgentOption to the opencode driver, or at least don't fall back to options[0] for drivers other than opencode.

Impact

Minor bug or occasional failure

Version or commit

Desktop 0.0.46-nightly.20261007.2774; source checked at main @ a8c4802

Environment

Linux (WSL2, kernel 6.18.40.1-microsoft-standard-WSL2), T3 Code nightly desktop, GitHub Copilot CLI 1.0.93 through the ACP registry (acpRegistry_github_copilot), model claude-opus-5.5

Logs or stack traces

# Provider snapshot after the bug (~/.t3/caches/acpRegistry_github_copilot.json, agent descriptor)
{
  "id": "agent",
  "label": "Agent",
  "description": "Select a custom agent persona, or use the default Copilot agent.",
  "type": "select",
  "options": [
    { "id": "example", "label": "example", "description": "Example agent." }
  ],
  "currentValue": "example"
}

# Persisted thread modelSelection
{"instanceId":"acpRegistry_github_copilot","model":"claude-opus-5.5","options":[
  {"id":"mode","value":"https://agentclientprotocol.com/protocol/session-modes#agent"},
  {"id":"reasoning_effort","value":"high"},
  {"id":"agent","value":"example"},
  {"id":"allow_all","value":"on"}]}

Screenshots, recordings, or supporting files

No response

Workaround

Move every custom agent out of ~/.copilot/agents/ (and out of the repo's .github/agents/), then restart T3 Code. With no custom agents, Copilot reports only the "" choice. T3 drops it, which leaves the descriptor with no choices, so T3 hides the menu entirely and Copilot uses its default agent.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 7, 2026
  2. juliusmarminge commented on Oct 7, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed write-up. I checked it against main @ 41985b44 and the bug is confirmed in code.

    Confirmed on main

    1. The empty choice and its currentValue are both dropped. boundedOpaqueValue requires value.length > 0 (AcpSessionConfig.ts:28-29), and selectChoices skips any choice it rejects (:47-48). currentValue is only kept when it's truthy and matches a surviving choice (:104-111), so "" is lost there too. I fed your session/new agent option into acpProviderOptionDescriptors and got back one choice (example) and no currentValue. The live-config path goes through the same function (AcpRegistryProbe.ts:194, called from AcpRegistryAdapterV2.ts:219-223).
    2. The contracts have no way to represent "". ProviderOptionChoice.id, SelectProviderOptionDescriptor.currentValue and ProviderOptionSelectionValue are all TrimmedNonEmptyString (packages/contracts/src/model.ts:11, :28, :46).
    3. The opencode plan filter runs for every provider and picks the custom agent. getProviderModelCapabilities applies withoutPlanAgentOption to every provider whenever plan mode is off (providerModels.ts:64-67), and off is the default (settings.ts:446). When there's no currentValue, it falls back to isDefault ?? options[0] (:86-89). ACP choices never set isDefault, so example becomes the current value.
    4. That value gets saved for future threads. Changing any trait in the menu rebuilds the selection from the current value of every descriptor, including the defaulted agent (TraitsPicker.tsx:338-340, packages/shared/src/model.ts:269-286). It saves that selection with persistSticky: true (TraitsPicker.tsx:312-316), which writes stickyModelSelectionByProvider (composerDraftStore.ts:3270-3290), and applyStickyState seeds new drafts from it. The adapter then sends agent=example with setConfigOption (AcpAdapterV2.ts:6310-6321).

    Root cause: T3 treats an empty select value as invalid at every layer (server normalization, contracts, UI), but ACP lets an agent use "" as a real choice. Without that choice, the web fallback selects the first custom agent and saves it.

    Fix direction: the proposed fix looks right to me.

    • Map an exact "" choice to a reserved ID (such as __acp_default__) in selectChoices, and map currentValue: "" the same way at :104-111. Whitespace-only values should still be dropped, which the existing test at AcpSessionConfig.test.ts:82 covers.
    • Turn the reserved ID back into "" in AcpAdapterV2 before the advertised-values check and setConfigOption (:6310-6321).
    • A sentinel is a better choice than loosening the contracts to allow "". handleSelectChange also ignores empty values (TraitsPicker.tsx:346), so "" would be blocked in the UI as well.
    • Limit withoutPlanAgentOption to the opencode driver (getProviderModelCapabilities already gets provider), or at least drop the options[0] fallback for other drivers.
    • Anyone who already has agent=example saved will keep it until they re-select the default agent once after the fix.

    Workaround: move your custom agents out of ~/.copilot/agents/ and .github/agents/, then restart, as you described. With only the "" choice left, the descriptor has no choices and gets dropped (AcpSessionConfig.ts:96-98). A saved agent=example is then skipped because the session no longer advertises it (AcpAdapterV2.ts:6311-6318). Turning plan mode on doesn't help: it skips the options[0] fallback, but the default choice is still missing and a saved selection stays.

  3. added
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 7, 2026
  4. amv146 commented on Oct 9, 2026

    @amv146

    Was about to file this same issue myself but was working on the PR. My original approach was the one suggested which I created a PR for #17356. The better solution would probably be to allow empty values rather than treating them as invalid, but that looked to be a much larger and riskier change.

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