Repository navigation
[Bug]: ACP registry drops an agent's empty-valued default choice, so GitHub Copilot always runs a custom agent #16898
Description
Activity
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 7, 2026 Note
Grok responding on behalf of Julius.
Thanks for the detailed write-up. I checked it against
main@41985b44and the bug is confirmed in code.Confirmed on main
- The empty choice and its
currentValueare both dropped.boundedOpaqueValuerequiresvalue.length > 0(AcpSessionConfig.ts:28-29), andselectChoicesskips any choice it rejects (:47-48).currentValueis only kept when it's truthy and matches a surviving choice (:104-111), so""is lost there too. I fed yoursession/newagent option intoacpProviderOptionDescriptorsand got back one choice (example) and nocurrentValue. The live-config path goes through the same function (AcpRegistryProbe.ts:194, called fromAcpRegistryAdapterV2.ts:219-223). - The contracts have no way to represent
"".ProviderOptionChoice.id,SelectProviderOptionDescriptor.currentValueandProviderOptionSelectionValueare allTrimmedNonEmptyString(packages/contracts/src/model.ts:11,:28,:46). - The opencode plan filter runs for every provider and picks the custom agent.
getProviderModelCapabilitiesapplieswithoutPlanAgentOptionto every provider whenever plan mode is off (providerModels.ts:64-67), and off is the default (settings.ts:446). When there's nocurrentValue, it falls back toisDefault ?? options[0](:86-89). ACP choices never setisDefault, soexamplebecomes the current value. - 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 withpersistSticky: true(TraitsPicker.tsx:312-316), which writesstickyModelSelectionByProvider(composerDraftStore.ts:3270-3290), andapplyStickyStateseeds new drafts from it. The adapter then sendsagent=examplewithsetConfigOption(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__) inselectChoices, and mapcurrentValue: ""the same way at:104-111. Whitespace-only values should still be dropped, which the existing test atAcpSessionConfig.test.ts:82covers. - Turn the reserved ID back into
""inAcpAdapterV2before the advertised-values check andsetConfigOption(:6310-6321). - A sentinel is a better choice than loosening the contracts to allow
"".handleSelectChangealso ignores empty values (TraitsPicker.tsx:346), so""would be blocked in the UI as well. - Limit
withoutPlanAgentOptionto the opencode driver (getProviderModelCapabilitiesalready getsprovider), or at least drop theoptions[0]fallback for other drivers. - Anyone who already has
agent=examplesaved 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 savedagent=exampleis then skipped because the session no longer advertises it (AcpAdapterV2.ts:6311-6318). Turning plan mode on doesn't help: it skips theoptions[0]fallback, but the default choice is still missing and a saved selection stays.- The empty choice and its
- addedvia-triageFiled through npx t3 triageFiled through npx t3 triageand removedneeds-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 7, 2026 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.
Before submitting
Area
apps/server
Steps to reproduce
~/.copilot/agents/example.agent.md:acpRegistry, agentgithub-copilot-cli, CLI 1.0.93).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. Onceexampleis selected, there is no option to switch back to the default Copilot agent. Every new thread sendssession/set_config_option agent=example, so the custom agent's instructions and itstools:allowlist apply to every Copilot thread. With a restrictivetools: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/newresponse fromcopilot --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:
apps/server/src/provider/acp/AcpSessionConfig.ts:selectChoicespasses every choice value throughboundedOpaqueValue, which returnsundefinedfor an empty string (value.length > 0). That drops the""/ "Copilot" choice.currentValue: ""is falsy, so the descriptor ends up with nocurrentValueeither.Even without that check, the choice couldn't be stored:
ProviderOptionChoice.idandSelectProviderOptionDescriptor.currentValueinpackages/contracts/src/model.tsare bothTrimmedNonEmptyString.apps/web/src/providerModels.ts:withoutPlanAgentOption, added in fix(web): hide opencode's plan agent when legacy plan mode is off #6420 for opencode'splanagent, runs on every provider'sagentdescriptor wheneverplanModeEnabledis false, which is the default. When there is nocurrentValue, it falls back tooptions[0]?.id, which isexamplehere. With plan mode on, this fallback is skipped, but the default choice is still missing and any earlierexampleselection persists (see 3).agent=example. The ACP adapter callssetConfigOption("agent", "example"), and the live configuration is published back into the provider snapshot (~/.t3/caches/acpRegistry_github_copilot.jsonnow shows"currentValue": "example"with a single option). The composer also keepsagent=exampleas the last-used selection. This keeps the wrong default for future threads.Suggested fix
__acp_default__) inacpProviderOptionDescriptors, keep acurrentValueof""by mapping it the same way, and turn the reserved ID back into""beforesetConfigOption.withoutPlanAgentOptionto the opencode driver, or at least don't fall back tooptions[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.5Logs or stack traces
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.