Skip to content

fix: atomic provider and model selection with proper cancellation handling #2803

Open
ashprakasan wants to merge 5 commits intoantinomyhq:mainfrom
ashprakasan:fix/provider-and-model-change-atomically
Open

fix: atomic provider and model selection with proper cancellation handling #2803
ashprakasan wants to merge 5 commits intoantinomyhq:mainfrom
ashprakasan:fix/provider-and-model-change-atomically

Conversation

@ashprakasan
Copy link
Copy Markdown
Contributor

@ashprakasan ashprakasan commented Apr 2, 2026

Description:

Problem

Several issues existed in the provider and model selection flow:

  1. Non-atomic config writes (fixes [Bug]: Model and provider selction is not atomic on login #2714): When switching providers, set_default_provider was called before model selection was confirmed. If the user cancelled model selection, the config was left in an invalid state with a new provider but a model from the old provider.

  2. Infinite loop on new user onboarding: When no provider was set, select_model had an internal provider check that ran unconditionally even when called with a provider_filter. This caused a recursive loop: select_modelon_provider_selectionfinalize_provider_activationon_model_selectionselect_model → loop.

  3. Wrong order in init_state: Provider check ran after model check, causing model selection to be prompted before provider was configured.

  4. Cancel not propagated correctly: on_provider_selection returned Ok(()) on both cancel and success, making it impossible for callers to detect cancellation.

  5. forge commit bypassed init: The commit subcommand bypassed init_state entirely, causing a hard error when no provider was set instead of prompting the user.

Changes

  • finalize_provider_activation: Deferred set_default_provider to after model selection is confirmed. Added new set_default_provider_and_model atomic operation to write both together.
  • select_model: Guarded the internal provider check with provider_filter.is_none() to prevent recursive loops when called from finalize_provider_activation.
  • on_provider_selection: Changed return type from Result<()> to Result<bool> to signal whether a provider was actually saved. Returns false if the overall flow didn't result in a saved config.
  • init_state: Moved provider check before model check. Added early return if user cancels provider selection.
  • TopLevelCommand::Commit: Added init_state call so forge commit prompts for provider+model if not configured.
  • set_default_provider_and_model: New atomic API method using ConfigOperation::SetModel which writes both provider and model in a single operation.

Manual Testing

Scenario 1: New user — complete flow

Run forge with no config, select provider, complete auth, select model.
Result: Config shows correct provider and model. ✓

Scenario 2: New user — cancel at provider selection

Run forge with no config, press ESC at provider list.
Result: Exits cleanly, nothing written to config. ✓

Scenario 3: New user — cancel at model selection

Run forge with no config, select provider, complete auth, say yes to default, press ESC at model selection.
Result: Exits cleanly, nothing written to config. ✓

Scenario 4: Existing user — cancel at model selection during provider login

Run forge provider login, select different provider, complete auth, say yes to default, press ESC at model selection.
Result: Config unchanged — original provider and model preserved. ✓

Scenario 5: Existing user — complete provider switch

Run forge provider login, select different provider, complete auth, say yes, select model.
Result: Config shows new provider and model. ✓

Scenario 6: forge commit — no provider set

Run forge commit with no config.
Result: Provider and model selection prompted before commit proceeds. ✓

Scenario 7: forge commit — provider and model already set

Run forge commit with config already set.
Result: No prompts, commit proceeds directly. ✓

Scenario 8: New user — interactive mode

Run forge with no config, select provider, complete auth, say yes to default, select model.
Result: Enters interactive mode successfully, provider and model set correctly. ✓

Scenario 9: New user — interactive mode, cancel at model selection

Run forge with no config, select provider, complete auth, say yes to default, press ESC at model selection.
Result: Exits cleanly, nothing written to config. ✓

Notes

A follow-up PR will introduce the necessary abstractions to make this flow unit-testable (mockable ForgeWidget or extraction of pure logic), as noted in the existing test module comment.

Fixes #2714

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 2, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ tusharmath
✅ amitksingh1490
❌ Aiswarya Prakasan


Aiswarya Prakasan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions bot added the type: fix Iterations on existing features or infrastructure. label Apr 2, 2026
@ashprakasan
Copy link
Copy Markdown
Contributor Author

Testing with harness script

The following script simulates a new user with a clean home directory:

#!/usr/bin/env zsh
set -euo pipefail
FORGE_BIN="${FORGE_BIN:-/Users/aiswaryaprakasan/Projects/forge/target/debug/forge}"
TEST_HOME="$(mktemp -d)"
echo "TEST_HOME created: $TEST_HOME"
TEST_CWD="$(mktemp -d)"
echo "TEST_CWD created: $TEST_CWD"
cleanup() {
echo "--- Config after login ---"
  env -i \
    HOME="$TEST_HOME" \
    PATH="$PATH" \
    SHELL="${SHELL:-/bin/zsh}" \
    USER="${USER:-$(id -un)}" \
    "$FORGE_BIN" config get provider || echo "Provider: Not set"
  env -i \
    HOME="$TEST_HOME" \
    PATH="$PATH" \
    SHELL="${SHELL:-/bin/zsh}" \
    USER="${USER:-$(id -un)}" \
    "$FORGE_BIN" config get model || echo "Model: Not set"
  echo "cleanup is called"
  rm -rf "$TEST_HOME" "$TEST_CWD"
}
trap cleanup EXIT
cd "$TEST_CWD"
env -i \
  HOME="$TEST_HOME" \
  PATH="$PATH" \
  SHELL="${SHELL:-/bin/zsh}" \
  USER="${USER:-$(id -un)}" \
  "$FORGE_BIN" -p "hello" "$@"

Run:

# New user interactive mode, change the last line to
"$FORGE_BIN" commit "$@"

# New user provider login, change the last line to 
"$FORGE_BIN" provider login "$@"
  1. How to build and test — add:
git checkout <branch>
cargo build
# Then use the harness script above for new user scenarios
# For existing user scenarios use ./target/debug/forge directly

@ashprakasan ashprakasan force-pushed the fix/provider-and-model-change-atomically branch from ee65bd2 to 1f24fe8 Compare April 3, 2026 06:56
@ashprakasan
Copy link
Copy Markdown
Contributor Author

Screenshot 2026-04-03 at 3 26 53 PM

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

Labels

type: fix Iterations on existing features or infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Model and provider selction is not atomic on login

4 participants