VulCode
Blog

Zed: wolny katalog modeli ChatGPT nie blokuje już logowania

Katalog modeli z subskrypcji ChatGPT potrafi odpowiadać 30 sekund, a Zed czekał 5 i chował modele konta. Teraz logowanie czeka 5 sekund, a katalog doładowuje się w tle.

DualFroz - VulCode CEODualFroz - VulCode CEO1 min czytania
Zed: wolny katalog modeli ChatGPT nie blokuje już logowania - DualFroz - VulCode CEO
Tytuł zmiany w repozytorium
openai_subscribed: Keep loading slow model catalogs in the background
Scalono
1.10.2026
Opisano tutaj
6.10.2026

Wpis powstał 5 dni po scaleniu zmiany.

Zed
Technologie:Rustasync

Co nie działało

Zed, edytor kodu napisany w Rust, pozwala korzystać z modeli z subskrypcji ChatGPT. Po zalogowaniu pobiera katalog modeli dostępnych dla danego konta z endpointu /backend-api/codex/models. Dla części kont ta odpowiedź przychodzi po około 30 sekundach, a Zed przerywał zapytanie po 5. Wracał wtedy do wbudowanej listy modeli i z selektora znikały modele przypisane do konta (zgłoszenie #64173).

Samo wydłużenie limitu nie wchodziło w grę. Ładowanie danych logowania i samo logowanie czekają na wynik tego zapytania, więc użytkownik patrzyłby na zawieszony ekran przez pół minuty.

Co zmieniliśmy

Rozdzieliliśmy dwa limity czasu. Logowanie dalej czeka najwyżej 5 sekund, ale zapytanie nie jest przerywane: działa w tle do 60 sekund i podmienia listę modeli, kiedy wreszcie wróci.

rust
futures::select! {
    result = refresh => log_model_catalog_error(result),
    () = wait_timeout => {
        cx.spawn(async move |_| log_model_catalog_error(refresh.await))
            .detach();
    }
}

Jeśli katalog zdąży w 5 sekund, wszystko działa jak wcześniej. Jeśli nie, odpięte zadanie kończy się później, a ewentualny błąd trafia tylko do logu, bez blokowania interfejsu.

Jak to sprawdziliśmy

Dodaliśmy test test_slow_model_catalog_does_not_block_initial_load z udawanym klientem HTTP, który wstrzymuje odpowiedź. Pierwsze ładowanie kończy się po 5 sekundach z wbudowanymi modelami, a odpowiedź, która przychodzi 30 sekund później, podmienia listę na modele konta. Przeszły też cargo test i clippy dla tej paczki. Na prawdziwym endpointcie zmiana nie była testowana i napisaliśmy to wprost w opisie pull requesta.

Efekt

Zmianę scalono do gałęzi main 1 października 2026. Wpis do notatek wydania opisuje ją jako poprawkę znikających modeli z subskrypcji ChatGPT, gdy katalog ładuje się dłużej niż 5 sekund.

Szybki edytor kodu w Ruście od twórców Atoma i Tree-sittera, z pracą zespołową na żywo.

Język
Rust
Gwiazdki na GitHubie
91 587

Zmiana w kodzie projektu Zed

Fragmenty pull requesta #64925 tak, jak pokazuje je git, z przypisami naszymi słowami: co było, co jest i dlaczego. Obok test, który to sprawdza.

zed-industries/zed#64925
Pull request
scalone 1.10.2026crates/openai_subscribed/src/openai_subscribed.rs+123-10 w 1 pliku
@@ -46,2 +46,8 @@ const TOKEN_REFRESH_BUFFER_MS: u64 = Duration::from_mins(5).as_millis() as u64;
46-// Codex applies the same bound because model discovery is a startup-critical request.
47-const MODEL_CATALOG_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
46+/// How long credential loading and sign-in wait for model discovery before
47+/// continuing with the models known so far.
48+///
49+/// Model discovery is startup-critical, so this matches the 5s bound Codex uses.
50+const MODEL_CATALOG_WAIT_TIMEOUT: Duration = Duration::from_secs(5);
51+/// The authenticated catalog can take tens of seconds to respond, so the request
52+/// keeps running in the background well past [`MODEL_CATALOG_WAIT_TIMEOUT`].
53+const MODEL_CATALOG_REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
@@ -146,6 +152,4 @@ impl State {
146152})?;
147-if let Some(refresh_models_task) = refresh_models_task
148- && let Err(error) = refresh_models_task.await
149-{
150- log::warn!("Failed to refresh ChatGPT models: {error:#}");
153+if let Some(refresh_models_task) = refresh_models_task {
154+ wait_for_model_catalog(refresh_models_task, cx).await;
151155}
@@ -1164,0 +1171,14 @@ async fn exchange_code(
wyżej 4 dodane linie komentarza do funkcji
1171+async fn wait_for_model_catalog(refresh: Task<Result<(), Arc<anyhow::Error>>>, cx: &mut AsyncApp) {
1172+ let mut refresh = refresh.fuse();
1173+ let mut wait_timeout = cx
1174+ .background_executor()
1175+ .timer(MODEL_CATALOG_WAIT_TIMEOUT)
1176+ .fuse();
1177+ futures::select! {
1178+ result = refresh => log_model_catalog_error(result),
1179+ () = wait_timeout => {
1180+ cx.spawn(async move |_| log_model_catalog_error(refresh.await))
1181+ .detach();
1182+ }
1183+ }
1184+}
dalej 7 dodanych linii: log_model_catalog_error zapisuje błąd z tła w logu

Przypisy do zmiany

  1. Dwa limity zamiast jednegolinie 50-53Logowanie dalej czeka na katalog najwyżej 5 sekund, tyle co Codex. Samo zapytanie dostaje 60 sekund, bo dla części kont katalog odpowiada po około 30.
  2. Tu logowanie stałolinie 147-150, przed zmianąŁadowanie danych logowania czekało, aż zapytanie o katalog się skończy. Po 5 sekundach Zed je przerywał, wracał do wbudowanej listy i z wyboru znikały modele przypisane do konta.
  3. Wygrywa to, co skończy się pierwszelinie 1177-1179Katalog albo 5 sekund. Jeśli katalog zdąży, wszystko działa jak przed zmianą.
  4. Reszta dzieje się w tlelinie 1180-1181Po 5 sekundach zapytanie nie jest przerywane. Odpięte zadanie czeka na katalog do 60 sekund i podmienia listę modeli, a ewentualny błąd trafia tylko do logu.

Jak to sprawdziliśmy

Do pull requesta doszedł test z udawanym klientem HTTP, który wstrzymuje odpowiedź z katalogiem. Przeszły też cargo test i clippy dla tej paczki.

test_slow_model_catalog_does_not_block_initial_load
  1. 0 sStart logowaniaKlient HTTP trzyma odpowiedź z katalogiem modeli.
  2. 5 sLogowanie się kończyKonto jest zalogowane, w wyborze są wbudowane modele.
  3. 30 sKatalog dochodziOdpowiedź podmienia listę na model konta, bez błędu katalogu.

Na prawdziwym endpointcie zmiana nie była testowana i napisaliśmy to wprost w opisie pull requesta.

Czego nas to nauczyło

Wolne zapytanie sieciowe nie może blokować logowania. Rzeczy, które nie są potrzebne od razu, doczytujemy w tle.

Pytania o zmianę #64925

Gdzie jest zmiana, jak ją sprawdziliśmy i kto ją przyjął.