fix: flush extension provider registrations before model resolution (#1923)

Extension-based providers like pi-claude-cli register their models
during extension loading, but registrations were queued and not flushed
until after model resolution ran. This caused findInitialModel() and
the startup model validation to see extension models as nonexistent,
permanently overwriting the user's saved model selection on every launch.

- Flush pendingProviderRegistrations in createAgentSession() before
  findInitialModel() so extension models are visible in the registry
- Move model validation to after createAgentSession() in both print
  and interactive code paths
- Load extensions before --list-models so extension models appear
This commit is contained in:
Rebecca Chernoff 2026-04-13 06:06:16 -05:00 committed by GitHub
parent 416be1e169
commit 110c01b8c6
2 changed files with 78 additions and 1 deletions

View file

@ -219,6 +219,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
time("resourceLoader.reload"); time("resourceLoader.reload");
} }
// Flush provider registrations queued during extension loading so that
// extension models (e.g. pi-claude-cli) are visible in the registry before
// findInitialModel() runs. bindCore() repeats this flush as a safety net
// for any late-arriving registrations.
const { runtime: extensionRuntime } = resourceLoader.getExtensions();
for (const { name, config } of extensionRuntime.pendingProviderRegistrations) {
modelRegistry.registerProvider(name, config);
}
extensionRuntime.pendingProviderRegistrations = [];
// Check if session has existing data to restore // Check if session has existing data to restore
const existingSession = sessionManager.buildSessionContext(); const existingSession = sessionManager.buildSessionContext();
const hasExistingSession = existingSession.messages.length > 0; const hasExistingSession = existingSession.messages.length > 0;

View file

@ -131,6 +131,48 @@ function parseCliArgs(argv: string[]): CliFlags {
return flags return flags
} }
/**
* Validate the configured default model against the registry and reset it if
* it no longer exists. Must run AFTER extensions have registered their
* providers so that extension models (e.g. pi-claude-cli) are visible.
*/
function validateConfiguredModel(
modelRegistry: ModelRegistry,
settingsManager: SettingsManager,
): void {
const configuredProvider = settingsManager.getDefaultProvider()
const configuredModel = settingsManager.getDefaultModel()
const allModels = modelRegistry.getAll()
const availableModels = modelRegistry.getAvailable()
const configuredExists = configuredProvider && configuredModel &&
allModels.some((m) => m.provider === configuredProvider && m.id === configuredModel)
const configuredAvailable = configuredProvider && configuredModel &&
availableModels.some((m) => m.provider === configuredProvider && m.id === configuredModel)
if (!configuredModel || !configuredExists) {
// Model not configured at all, or removed from registry — pick a fallback.
// Only fires when the model is genuinely unknown (not just temporarily unavailable).
const piDefault = getPiDefaultModelAndProvider()
const preferred =
(piDefault
? availableModels.find((m) => m.provider === piDefault.provider && m.id === piDefault.model)
: undefined) ||
availableModels.find((m) => m.provider === 'openai' && m.id === 'gpt-5.4') ||
availableModels.find((m) => m.provider === 'openai') ||
availableModels.find((m) => m.provider === 'anthropic' && m.id === 'claude-opus-4-6') ||
availableModels.find((m) => m.provider === 'anthropic' && m.id.includes('opus')) ||
availableModels.find((m) => m.provider === 'anthropic') ||
availableModels[0]
if (preferred) {
settingsManager.setDefaultModelAndProvider(preferred.provider, preferred.id)
}
}
if (settingsManager.getDefaultThinkingLevel() !== 'off' && !configuredExists) {
settingsManager.setDefaultThinkingLevel('off')
}
}
const cliFlags = parseCliArgs(process.argv) const cliFlags = parseCliArgs(process.argv)
const isPrintMode = cliFlags.print || cliFlags.mode !== undefined const isPrintMode = cliFlags.print || cliFlags.mode !== undefined
@ -382,8 +424,23 @@ if (!isPrintMode && process.stdout.columns && process.stdout.columns < 40) {
) )
} }
// --list-models: print available models and exit (no TTY needed) // --list-models: load extensions so that extension-registered providers (e.g.
// pi-claude-cli) appear in the listing, then flush their pending registrations
// into the model registry before printing.
if (cliFlags.listModels !== undefined) { if (cliFlags.listModels !== undefined) {
exitIfManagedResourcesAreNewer(agentDir)
initResources(agentDir)
const listModelsLoader = new DefaultResourceLoader({
agentDir,
additionalExtensionPaths: cliFlags.extensions.length > 0 ? cliFlags.extensions : undefined,
})
await listModelsLoader.reload()
const listModelsExtensions = listModelsLoader.getExtensions()
for (const { name, config } of listModelsExtensions.runtime.pendingProviderRegistrations) {
modelRegistry.registerProvider(name, config)
}
listModelsExtensions.runtime.pendingProviderRegistrations = []
const models = modelRegistry.getAvailable() const models = modelRegistry.getAvailable()
if (models.length === 0) { if (models.length === 0) {
console.log('No models available. Set API keys in environment variables.') console.log('No models available. Set API keys in environment variables.')
@ -532,6 +589,11 @@ if (isPrintMode) {
} }
} }
// Validate configured model now that extension providers are registered.
// Must run after createAgentSession() which flushes pendingProviderRegistrations
// so extension models (e.g. pi-claude-cli) are visible in the registry.
validateConfiguredModel(modelRegistry, settingsManager)
// Apply --model override if specified // Apply --model override if specified
if (cliFlags.model) { if (cliFlags.model) {
const available = modelRegistry.getAvailable() const available = modelRegistry.getAvailable()
@ -738,6 +800,11 @@ if (extensionsResult.errors.length > 0) {
} }
} }
// Validate configured model now that extension providers are registered.
// Must run after createAgentSession() which flushes pendingProviderRegistrations
// so extension models (e.g. pi-claude-cli) are visible in the registry.
validateConfiguredModel(modelRegistry, settingsManager)
// Restore scoped models from settings on startup. // Restore scoped models from settings on startup.
// The upstream InteractiveMode reads enabledModels from settings when /scoped-models is opened, // The upstream InteractiveMode reads enabledModels from settings when /scoped-models is opened,
// but doesn't apply them to the session at startup — so Ctrl+P cycles all models instead of // but doesn't apply them to the session at startup — so Ctrl+P cycles all models instead of